diff --git a/README.md b/README.md index ff8ad2a66a..b8d668297d 100644 --- a/README.md +++ b/README.md @@ -658,7 +658,7 @@ Example output: 🟢 🟢 🟢 - 🔴 + 🟢 🔴 @@ -698,7 +698,14 @@ Example output: - GPU usage and memory usage shows usage of current process, not total system usage (it's an issue on intel's side) - https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/14153 - https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/4861 -- Integrated Intel GPUs are **limited** due to lack of hwmon interface (it's an issue on intel's side, [i915 source](https://github.com/torvalds/linux/blob/5fc31936081919a8572a3d644f3fbb258038f337/drivers/gpu/drm/i915/i915_hwmon.c#L914-L916), [xe source](https://github.com/torvalds/linux/blob/5fc31936081919a8572a3d644f3fbb258038f337/drivers/gpu/drm/xe/xe_hwmon.c#L824-L826)) +- For integrated Intel GPUs, the `vram` overlay row uses current-process + fdinfo shared memory (`drm-resident-system0` on `i915`, + `drm-resident-gtt` on `xe`) when local or VRAM fdinfo memory is not + available. This keeps SteamOS `mangoapp` presets useful without claiming + total system VRAM support. +- Integrated Intel GPU power uses the RAPL `uncore` powercap energy counter when + it is present and readable by the current user. +- Integrated Intel GPUs are otherwise **limited** due to lack of hwmon interface (it's an issue on intel's side, [i915 source](https://github.com/torvalds/linux/blob/5fc31936081919a8572a3d644f3fbb258038f337/drivers/gpu/drm/i915/i915_hwmon.c#L914-L916), [xe source](https://github.com/torvalds/linux/blob/5fc31936081919a8572a3d644f3fbb258038f337/drivers/gpu/drm/xe/xe_hwmon.c#L824-L826)) #### Panfrost and Panthor notes - GPU usage requires `echo N | sudo tee /sys/class/drm/renderD*/device/profiling` diff --git a/src/app/frame_feed.cpp b/src/app/frame_feed.cpp new file mode 100644 index 0000000000..37ad971184 --- /dev/null +++ b/src/app/frame_feed.cpp @@ -0,0 +1,287 @@ +// Frame-feed exporter for the steamos-intel-handheld game-power daemon. +// See frame_feed.h and docs/game-power-v10-framework-plan.md (contract 1.1). +// +// Design notes: +// * Zero cost when MANGOAPP_FRAME_FEED != "1": the first statement is a +// cached state check. +// * Single-threaded: only mangoapp's msgrcv loop calls +// frame_feed_record_frame(), so the rolling window needs no locking. +// * No timer thread: the 2 Hz cadence is driven off the frame callbacks +// themselves (write on the first frame >= 500 ms after the last write). +// * Fail-closed: any I/O error logs once to stderr and disables the feed +// for the rest of the session. No exception escapes into the caller. + +#include "frame_feed.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +// Rolling window bounds (contract 1.1: last 2.0 s or last N=256 frames, +// whichever is smaller). +constexpr double kWindowSeconds = 2.0; +constexpr size_t kMaxFrames = 256; + +// Minimum spacing between file writes (~2 Hz). +constexpr double kWriteIntervalSeconds = 0.5; + +// A frame that exceeds this multiple of the window median counts as a spike. +constexpr double kSpikeRatio = 1.5; + +// Guard against absurd frame times poisoning the window (matches the spirit +// of the overlay's own >100 s guard; here 10 s in ms). +constexpr float kMaxSaneFrameMs = 10000.0f; + +enum class FeedState { kUninitialized, kEnabled, kDisabled }; + +struct Sample { + double t_s; // CLOCK_MONOTONIC timestamp of the frame + float ms; // visible frame time in milliseconds +}; + +FeedState g_state = FeedState::kUninitialized; +std::string g_path; // final target path +std::string g_tmp_path; // temp path in the same directory +double g_last_write_s = 0.0; +std::deque g_window; + +double monotonic_seconds() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec) * 1e-9; +} + +void disable_with_error(const char* what) { + // Logged exactly once (state flips to kDisabled here). + fprintf(stderr, + "mangoapp frame-feed: disabled for this session (%s: %s)\n", + what, strerror(errno)); + g_state = FeedState::kDisabled; +} + +// Create every missing component of the directory that holds the feed file, +// each with mode 0700. Returns false only on a real error (EEXIST is fine). +bool ensure_parent_dir(const std::string& file_path) { + std::string::size_type slash = file_path.find_last_of('/'); + if (slash == std::string::npos || slash == 0) + return true; // relative name or root-level file: nothing to make + std::string dir = file_path.substr(0, slash); + + // Walk the path creating each component. + for (std::string::size_type i = 1; i <= dir.size(); ++i) { + if (i == dir.size() || dir[i] == '/') { + std::string component = dir.substr(0, i); + if (mkdir(component.c_str(), 0700) != 0 && errno != EEXIST) + return false; + } + } + return true; +} + +// Resolve the target path and prepare the directory. Returns false to leave +// the feed disabled. Returns silently (no log) when the env var is unset; +// logs once on a genuine setup failure. +bool feed_init() { + const char* enable = getenv("MANGOAPP_FRAME_FEED"); + if (!enable || strcmp(enable, "1") != 0) + return false; // feature off: stay silent + + const char* override_path = getenv("MANGOAPP_FRAME_FEED_FILE"); + if (override_path && override_path[0] != '\0') { + g_path = override_path; + } else { + const char* runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!runtime_dir || runtime_dir[0] == '\0') { + fprintf(stderr, + "mangoapp frame-feed: disabled (MANGOAPP_FRAME_FEED=1 but " + "XDG_RUNTIME_DIR is unset and MANGOAPP_FRAME_FEED_FILE was " + "not provided)\n"); + return false; + } + g_path = std::string(runtime_dir) + + "/steamos-intel-handheld/frame-feed.json"; + } + + if (!ensure_parent_dir(g_path)) { + disable_with_error("mkdir"); + // disable_with_error already set kDisabled; return false so the caller + // does not flip us back to kEnabled. + return false; + } + + // Temp file lives in the same directory (same filesystem) so rename() is + // atomic. Namespace it by pid to avoid collisions between processes. + g_tmp_path = g_path + "." + std::to_string(static_cast(getpid())) + + ".tmp"; + return true; +} + +void trim_window(double now_s) { + const double cutoff = now_s - kWindowSeconds; + while (!g_window.empty() && + (g_window.front().t_s < cutoff || g_window.size() > kMaxFrames)) { + g_window.pop_front(); + } +} + +// Atomically replace g_path with `json` via temp-file + rename. Returns +// false on any I/O error (caller disables the feed). +bool write_atomic(const std::string& json) { + int fd = open(g_tmp_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + return false; + + const char* p = json.data(); + size_t remaining = json.size(); + while (remaining > 0) { + ssize_t w = write(fd, p, remaining); + if (w < 0) { + if (errno == EINTR) + continue; + close(fd); + unlink(g_tmp_path.c_str()); + return false; + } + p += w; + remaining -= static_cast(w); + } + close(fd); + + if (rename(g_tmp_path.c_str(), g_path.c_str()) != 0) { + unlink(g_tmp_path.c_str()); + return false; + } + return true; +} + +// Compute stats over the current window and, if valid, write them. Returns +// false only on an I/O error that should disable the feed. +bool compute_and_write(double now_s, uint32_t app_pid) { + const size_t n = g_window.size(); + if (n == 0) + return true; // nothing to report this interval; not an error + + std::vector sorted; + sorted.reserve(n); + double sum_ms = 0.0; + float worst_ms = 0.0f; + for (const Sample& s : g_window) { + sorted.push_back(s.ms); + sum_ms += s.ms; + if (s.ms > worst_ms) + worst_ms = s.ms; + } + std::sort(sorted.begin(), sorted.end()); + + const float last_frame_ms = g_window.back().ms; + + // p95 frame time: value at the 95th percentile (ascending, high = slow). + size_t p95_idx = static_cast(std::lround(0.95 * (n - 1))); + if (p95_idx >= n) + p95_idx = n - 1; + const float p95_frame_ms = sorted[p95_idx]; + + // Median for the spike threshold. + const float median_ms = + (n % 2 == 1) ? sorted[n / 2] + : 0.5f * (sorted[n / 2 - 1] + sorted[n / 2]); + + const double mean_ms = sum_ms / static_cast(n); + const double avg_fps = (mean_ms > 0.0) ? (1000.0 / mean_ms) : 0.0; + + // Skip this interval if the core metrics are not finite positive. + if (!std::isfinite(avg_fps) || avg_fps <= 0.0 || + !std::isfinite(p95_frame_ms) || p95_frame_ms <= 0.0f) + return true; + + // Spike count: frames exceeding kSpikeRatio * median. + unsigned spike_count = 0; + if (median_ms > 0.0f) { + const float threshold = kSpikeRatio * median_ms; + for (const Sample& s : g_window) + if (s.ms > threshold) + ++spike_count; + } + + // Actual span covered by the window (target 2.0 s). + const double window_s = g_window.back().t_s - g_window.front().t_s; + + char buf[512]; + int len = snprintf( + buf, sizeof(buf), + "{" + "\"schema\":\"steamos-intel-handheld-frame-feed-v1\"," + "\"pid\":%ld," + "\"updated_monotonic_s\":%.3f," + "\"window_s\":%.3f," + "\"frame_count\":%zu," + "\"avg_fps\":%.2f," + "\"p95_frame_ms\":%.2f," + "\"last_frame_ms\":%.2f," + "\"spike\":{\"count\":%u,\"worst_ms\":%.2f}" + "}\n", + static_cast(app_pid != 0 ? app_pid + : static_cast(getpid())), + now_s, window_s, n, avg_fps, static_cast(p95_frame_ms), + static_cast(last_frame_ms), spike_count, + static_cast(worst_ms)); + if (len < 0 || len >= static_cast(sizeof(buf))) + return true; // formatting glitch; skip this interval, keep the feed + + return write_atomic(std::string(buf, static_cast(len))); +} + +} // namespace + +void frame_feed_record_frame(uint64_t visible_frametime_ns, uint32_t app_pid) { + if (g_state == FeedState::kDisabled) + return; + + try { + if (g_state == FeedState::kUninitialized) { + if (!feed_init()) { + // feed_init leaves g_state as kUninitialized when the feature + // is simply off, or kDisabled after logging a real error. + if (g_state != FeedState::kDisabled) + g_state = FeedState::kDisabled; // off: never retry + return; + } + g_state = FeedState::kEnabled; + } + + const float frame_ms = static_cast(visible_frametime_ns) / 1e6f; + if (!(frame_ms > 0.0f) || frame_ms > kMaxSaneFrameMs) + return; // drop non-positive / absurd frames, keep the feed live + + const double now_s = monotonic_seconds(); + g_window.push_back(Sample{now_s, frame_ms}); + trim_window(now_s); + + if (now_s - g_last_write_s < kWriteIntervalSeconds) + return; + g_last_write_s = now_s; + + if (!compute_and_write(now_s, app_pid)) + disable_with_error("write"); + } catch (...) { + // Nothing may propagate into the render/message path. + fprintf(stderr, + "mangoapp frame-feed: disabled after unexpected exception\n"); + g_state = FeedState::kDisabled; + } +} diff --git a/src/app/frame_feed.h b/src/app/frame_feed.h new file mode 100644 index 0000000000..314b299ab8 --- /dev/null +++ b/src/app/frame_feed.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +// Frame-feed exporter for the steamos-intel-handheld game-power daemon. +// +// Entirely guarded by the env var MANGOAPP_FRAME_FEED=1 (read once and +// cached). When the feed is disabled the call is a single cached-state +// comparison and returns immediately: no allocation, no I/O, no overhead. +// +// When enabled it maintains a rolling window of the most recent frame +// times and, at ~2 Hz, atomically writes a compact JSON summary (see +// docs/game-power-v10-framework-plan.md contract 1.1) to +// $XDG_RUNTIME_DIR/steamos-intel-handheld/frame-feed.json (overridable via +// MANGOAPP_FRAME_FEED_FILE). +// +// Must be called once per visible game frame from the mangoapp message +// read path. It is single-threaded by contract (only the msgrcv loop calls +// it) and never throws into the caller. +void frame_feed_record_frame(uint64_t visible_frametime_ns, uint32_t app_pid); diff --git a/src/app/main.cpp b/src/app/main.cpp index 19c465fbea..43986b102a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" @@ -17,6 +18,7 @@ #include "notify.h" #include "mangoapp.h" #include "mangoapp_proto.h" +#include "frame_feed.h" #include #ifdef __linux__ #include "implot.h" @@ -206,6 +208,13 @@ static void msg_read_thread(){ should_new_frame = true; } + // Export a rolling frame summary for the steamos-intel-handheld + // game-power daemon. No-op unless MANGOAPP_FRAME_FEED=1. Runs + // regardless of overlay visibility so the daemon still gets frame + // data when the HUD is hidden. + if (mangoapp_v1->visible_frametime_ns != ~(0lu)) + frame_feed_record_frame(mangoapp_v1->visible_frametime_ns, mangoapp_v1->pid); + if (msg_size > offsetof(mangoapp_msg_v1, fsrUpscale)){ HUDElements.g_fsrUpscale = mangoapp_v1->fsrUpscale; if (real_params->fsr_steam_sharpness < 0) diff --git a/src/gl/gl.h b/src/gl/gl.h index 76a621230c..4df7190f6a 100644 --- a/src/gl/gl.h +++ b/src/gl/gl.h @@ -2,10 +2,30 @@ #ifndef MANGOHUD_GL_GL_H #define MANGOHUD_GL_GL_H +#include + +typedef unsigned int GLenum; +typedef unsigned int GLbitfield; +typedef unsigned char GLubyte; +typedef float GLfloat; + +#define GL_DEPTH_TEST 0x0B71 +#define GL_BLEND 0x0BE2 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_RENDERER 0x1F01 + #ifdef __cplusplus extern "C" { #endif //__cplusplus +const GLubyte* glGetString(GLenum name); +void glEnable(GLenum cap); +void glBlendFunc(GLenum sfactor, GLenum dfactor); +void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void glClear(GLbitfield mask); + void * glXCreateContext(void *, void *, void *, int); void glXDestroyContext(void *, void*); void glXSwapBuffers(void*, void*); diff --git a/src/gpu.h b/src/gpu.h index 675d64f8a9..4480930601 100644 --- a/src/gpu.h +++ b/src/gpu.h @@ -100,6 +100,13 @@ class GPU { return false; } + bool uses_integrated_memory() const { + if (fdinfo) + return fdinfo->uses_integrated_memory(); + else + return false; + } + std::shared_ptr throttling() { if (nvidia) return nvidia->throttling; diff --git a/src/gpu_fdinfo.cpp b/src/gpu_fdinfo.cpp index 0fdd99e836..4e59448460 100644 --- a/src/gpu_fdinfo.cpp +++ b/src/gpu_fdinfo.cpp @@ -1,4 +1,5 @@ #include "gpu_fdinfo.h" +#include "file_utils.h" #ifndef TEST_ONLY #include "hud_elements.h" @@ -109,6 +110,39 @@ void GPU_fdinfo::gather_fdinfo_data() { } } +bool GPU_fdinfo::fdinfo_data_has_key(const std::string& key) const +{ + for (const auto& fd : fdinfo_data) { + if (fd.find(key) != fd.end()) + return true; + } + + return false; +} + +void GPU_fdinfo::update_intel_memory_type() +{ + if (module != "i915" && module != "xe") + return; + + if (fdinfo_data.empty() || fdinfo_data_has_key(drm_memory_type)) + return; + + const auto old_type = drm_memory_type; + const auto integrated_type = module == "i915" ? "drm-resident-system0" : "drm-resident-gtt"; + + if (!fdinfo_data_has_key(integrated_type)) + return; + + drm_memory_type = integrated_type; + intel_gpu_integrated = true; + + SPDLOG_DEBUG( + "\"{}\" is not found, you probably have an integrated GPU. " + "Using \"{}\"", old_type, drm_memory_type + ); +} + uint64_t GPU_fdinfo::get_gpu_time() { uint64_t total = 0; @@ -303,11 +337,108 @@ void GPU_fdinfo::get_current_hwmon_readings() } } +bool GPU_fdinfo::has_hwmon_sensor(const std::string& key) const +{ + auto sensor = hwmon_sensors.find(key); + return sensor != hwmon_sensors.end() && sensor->second.stream.is_open(); +} + +void GPU_fdinfo::find_intel_gpu_rapl_power() +{ + std::string powercap = "/sys/class/powercap/"; + auto powercap_dirs = ls(powercap.c_str()); + + for (auto& dir : powercap_dirs) { + auto path = powercap + dir; + auto name = read_line(path + "/name"); + + if (name != "uncore") + continue; + + auto energy_path = path + "/energy_uj"; + auto energy_stream = std::ifstream(energy_path); + + if (!energy_stream.good()) { + SPDLOG_DEBUG( + "powercap: Intel GPU RAPL energy is not accessible at {}", + energy_path + ); + continue; + } + + intel_gpu_rapl_energy_stream = std::move(energy_stream); + + try { + auto max_energy_range = read_line(path + "/max_energy_range_uj"); + if (!max_energy_range.empty()) + intel_gpu_rapl_max_energy_range_uj = std::stoull(max_energy_range); + } catch (...) { + intel_gpu_rapl_max_energy_range_uj = 0; + } + + SPDLOG_DEBUG("powercap: using Intel GPU RAPL energy at {}", energy_path); + return; + } +} + +bool GPU_fdinfo::read_intel_gpu_rapl_energy(uint64_t& energy_uj) +{ + if (!intel_gpu_rapl_energy_stream.is_open()) + return false; + + intel_gpu_rapl_energy_stream.clear(); + intel_gpu_rapl_energy_stream.seekg(0); + + std::string energy; + std::getline(intel_gpu_rapl_energy_stream, energy); + + if (energy.empty()) + return false; + + try { + energy_uj = std::stoull(energy); + } catch (...) { + return false; + } + + return true; +} + +float GPU_fdinfo::get_intel_gpu_rapl_power_usage() +{ + uint64_t now = 0; + if (!read_intel_gpu_rapl_energy(now)) + return 0.f; + + if (!intel_gpu_rapl_energy_initialized) { + intel_gpu_rapl_last_energy_uj = now; + intel_gpu_rapl_energy_initialized = true; + return 0.f; + } + + uint64_t delta = 0; + if (now >= intel_gpu_rapl_last_energy_uj) { + delta = now - intel_gpu_rapl_last_energy_uj; + } else if (intel_gpu_rapl_max_energy_range_uj > intel_gpu_rapl_last_energy_uj) { + delta = intel_gpu_rapl_max_energy_range_uj - intel_gpu_rapl_last_energy_uj + now; + } + + intel_gpu_rapl_last_energy_uj = now; + + return (static_cast(delta) / (METRICS_UPDATE_PERIOD_MS / 1000.f)) / 1'000'000; +} + float GPU_fdinfo::get_power_usage() { - if (!hwmon_sensors["power"].filename.empty()) + if (intel_gpu_rapl_energy_stream.is_open()) + return get_intel_gpu_rapl_power_usage(); + + if (has_hwmon_sensor("power")) return static_cast(hwmon_sensors["power"].val) / 1'000'000; + if (!has_hwmon_sensor("energy")) + return -1.0f; + float now = hwmon_sensors["energy"].val; // Initialize value for the first time, otherwise delta will be very large @@ -540,7 +671,7 @@ int GPU_fdinfo::get_gpu_clock() return get_gpu_clock_mali(); if (!gpu_clock_stream.is_open()) - return 0; + return -1; std::string clock_str; @@ -549,14 +680,14 @@ int GPU_fdinfo::get_gpu_clock() std::getline(gpu_clock_stream, clock_str); if (clock_str.empty()) - return 0; + return -1; return std::stoi(clock_str); } int GPU_fdinfo::get_gpu_clock_mali() { if (fdinfo_data.empty()) - return 0; + return -1; std::string key; @@ -568,7 +699,7 @@ int GPU_fdinfo::get_gpu_clock_mali() { std::string freq_str = fdinfo_data[0][key]; if (freq_str.empty()) - return 0; + return -1; float freq = std::stoull(freq_str) / 1'000'000; @@ -688,7 +819,7 @@ int GPU_fdinfo::get_kgsl_temp() { std::ifstream* s = &kgsl_streams["temp"]; if (!s->is_open()) - return 0; + return -1; std::string temp_str; @@ -697,7 +828,7 @@ int GPU_fdinfo::get_kgsl_temp() { std::getline(*s, temp_str); if (temp_str.empty()) - return 0; + return -1; return std::round(std::stoi(temp_str) / 1'000.f); } @@ -726,26 +857,47 @@ void GPU_fdinfo::main_thread() } gather_fdinfo_data(); + update_intel_memory_type(); get_current_hwmon_readings(); metrics.load = get_gpu_load(); - metrics.proc_vram_used = get_memory_used(); + const float memory_used = get_memory_used(); + metrics.proc_vram_used = memory_used; + metrics.gtt_used = 0.0f; + if (intel_gpu_integrated) + metrics.gtt_used = memory_used; metrics.powerUsage = get_power_usage(); - metrics.powerLimit = static_cast(hwmon_sensors["power_limit"].val) / 1'000'000; + if (has_hwmon_sensor("power_limit")) + metrics.powerLimit = static_cast(hwmon_sensors["power_limit"].val) / 1'000'000; + else + metrics.powerLimit = -1.0f; metrics.CoreClock = get_gpu_clock(); - metrics.voltage = hwmon_sensors["voltage"].val; + if (has_hwmon_sensor("voltage")) + metrics.voltage = hwmon_sensors["voltage"].val; + else + metrics.voltage = -1; if (module == "msm_drm") metrics.temp = get_kgsl_temp(); - else + else if (has_hwmon_sensor("temp")) metrics.temp = hwmon_sensors["temp"].val / 1000.f; + else + metrics.temp = -1; - metrics.memory_temp = hwmon_sensors["vram_temp"].val / 1000.f; - - metrics.fan_speed = hwmon_sensors["fan_speed"].val; - metrics.fan_rpm = true; // Fan data is pulled from hwmon + if (has_hwmon_sensor("vram_temp")) + metrics.memory_temp = hwmon_sensors["vram_temp"].val / 1000.f; + else + metrics.memory_temp = -1; + + if (has_hwmon_sensor("fan_speed")) { + metrics.fan_speed = hwmon_sensors["fan_speed"].val; + metrics.fan_rpm = true; // Fan data is pulled from hwmon + } else { + metrics.fan_speed = -1; + metrics.fan_rpm = false; + } int throttling = get_throttling_status(); metrics.is_power_throttled = throttling & GPU_throttle_status::POWER; diff --git a/src/gpu_fdinfo.h b/src/gpu_fdinfo.h index 97e62e5265..957f9e0380 100644 --- a/src/gpu_fdinfo.h +++ b/src/gpu_fdinfo.h @@ -66,6 +66,8 @@ class GPU_fdinfo { std::vector> fdinfo_data; void gather_fdinfo_data(); + bool fdinfo_data_has_key(const std::string& key) const; + void update_intel_memory_type(); void main_thread(); @@ -86,10 +88,19 @@ class GPU_fdinfo { void find_hwmon_sensors(); std::string find_hwmon_dir(); std::string find_hwmon_sensor_dir(std::string name); + bool has_hwmon_sensor(const std::string& key) const; void get_current_hwmon_readings(); float get_power_usage(); float last_power = 0; + bool intel_gpu_integrated = false; + std::ifstream intel_gpu_rapl_energy_stream; + uint64_t intel_gpu_rapl_last_energy_uj = 0; + uint64_t intel_gpu_rapl_max_energy_range_uj = 0; + bool intel_gpu_rapl_energy_initialized = false; + void find_intel_gpu_rapl_power(); + bool read_intel_gpu_rapl_energy(uint64_t& energy_uj); + float get_intel_gpu_rapl_power_usage(); std::ifstream gpu_clock_stream; void find_i915_gt_dir(); @@ -156,21 +167,7 @@ class GPU_fdinfo { drm_memory_type = "drm-resident-memory"; } - if (fdinfo_data.size() > 0 && - fdinfo_data[0].find(drm_memory_type) == fdinfo_data[0].end()) - { - auto old_type = drm_memory_type; - - if (module == "i915") - drm_memory_type = "drm-resident-system0"; - else if (module == "xe") - drm_memory_type = "drm-resident-gtt"; - - SPDLOG_DEBUG( - "\"{}\" is not found, you probably have an integrated GPU. " - "Using \"{}\"", old_type, drm_memory_type - ); - } + update_intel_memory_type(); SPDLOG_DEBUG( "drm_engine_type = {}, drm_memory_type = {}", @@ -208,6 +205,9 @@ class GPU_fdinfo { find_hwmon_sensors(); + if (intel_gpu_integrated && !has_hwmon_sensor("energy") && !has_hwmon_sensor("power")) + find_intel_gpu_rapl_power(); + if (module == "i915") find_i915_gt_dir(); else if (module == "xe") @@ -242,4 +242,9 @@ class GPU_fdinfo { } float amdgpu_helper_get_proc_vram(); + + bool uses_integrated_memory() const + { + return intel_gpu_integrated; + } }; diff --git a/src/gpu_metrics_util.h b/src/gpu_metrics_util.h index a36411d73f..0b87a92bf6 100644 --- a/src/gpu_metrics_util.h +++ b/src/gpu_metrics_util.h @@ -3,16 +3,16 @@ struct gpu_metrics { int load; - int temp; + int temp {-1}; int junction_temp {-1}; int memory_temp {-1}; float sys_vram_used; float proc_vram_used; float memoryTotal; - int MemClock; - int CoreClock; - float powerUsage; - float powerLimit; + int MemClock {-1}; + int CoreClock {-1}; + float powerUsage {-1.0f}; + float powerLimit {-1.0f}; float apu_cpu_power; int apu_cpu_temp; bool is_power_throttled; @@ -20,17 +20,17 @@ struct gpu_metrics { bool is_temp_throttled; bool is_other_throttled; float gtt_used; - int fan_speed; - int voltage; + int fan_speed {-1}; + int voltage {-1}; bool fan_rpm; gpu_metrics() - : load(0), temp(0), junction_temp(0), memory_temp(0), - sys_vram_used(0.0f), proc_vram_used(0.0f), memoryTotal(0.0f), MemClock(0), CoreClock(0), - powerUsage(0.0f), powerLimit(0.0f), apu_cpu_power(0.0f), apu_cpu_temp(0), + : load(0), temp(-1), junction_temp(-1), memory_temp(-1), + sys_vram_used(0.0f), proc_vram_used(0.0f), memoryTotal(0.0f), MemClock(-1), CoreClock(-1), + powerUsage(-1.0f), powerLimit(-1.0f), apu_cpu_power(0.0f), apu_cpu_temp(0), is_power_throttled(false), is_current_throttled(false), is_temp_throttled(false), is_other_throttled(false), - gtt_used(0.0f), fan_speed(0), voltage(0), fan_rpm(false) {} + gtt_used(0.0f), fan_speed(-1), voltage(-1), fan_rpm(false) {} }; #define METRICS_UPDATE_PERIOD_MS 500 diff --git a/src/hud_elements.cpp b/src/hud_elements.cpp index 4dbf838acc..7268dd8fc6 100644 --- a/src/hud_elements.cpp +++ b/src/hud_elements.cpp @@ -311,7 +311,10 @@ void HudElements::gpu_stats(){ // ImGui::Text("%s", "%"); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_temp]){ + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_temp] && + gpu->metrics.temp > -1 + ){ ImguiNextColumnOrNewRow(); if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_temp_fahrenheit]) right_aligned_text(text_color, HUDElements.ralign_width, "%i", HUDElements.convert_to_fahrenheit(gpu->metrics.temp)); @@ -344,7 +347,11 @@ void HudElements::gpu_stats(){ ImGui::PopFont(); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_fan] && !gpu->is_apu()){ + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_fan] && + gpu->metrics.fan_speed > -1 && + !gpu->is_apu() + ){ ImguiNextColumnOrNewRow(); right_aligned_text(text_color, HUDElements.ralign_width, "%i", gpu->metrics.fan_speed); ImGui::SameLine(0, 1.0f); @@ -360,7 +367,10 @@ void HudElements::gpu_stats(){ ImGui::PopFont(); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_core_clock]){ + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_core_clock] && + gpu->metrics.CoreClock > -1 + ){ ImguiNextColumnOrNewRow(); right_aligned_text(text_color, HUDElements.ralign_width, "%i", gpu->metrics.CoreClock); ImGui::SameLine(0, 1.0f); @@ -369,7 +379,10 @@ void HudElements::gpu_stats(){ ImGui::PopFont(); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_power]) { + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_power] && + gpu->metrics.powerUsage > -1 + ) { ImguiNextColumnOrNewRow(); char str[16]; snprintf(str, sizeof(str), "%.1f", gpu->metrics.powerUsage); @@ -379,14 +392,20 @@ void HudElements::gpu_stats(){ right_aligned_text(text_color, HUDElements.ralign_width, "%.1f", gpu->metrics.powerUsage); ImGui::SameLine(0, 1.0f); ImGui::PushFont(HUDElements.sw_stats->font_small); - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_power_limit]) + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_power_limit] && + gpu->metrics.powerLimit > 0 + ) HUDElements.TextColored(HUDElements.colors.text, "/%.0fW", gpu->metrics.powerLimit); else HUDElements.TextColored(HUDElements.colors.text, "W"); ImGui::PopFont(); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_efficiency]) { + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_efficiency] && + gpu->metrics.powerUsage > 0 + ) { ImguiNextColumnOrNewRow(); float efficiency; const char* efficiency_unit; @@ -404,7 +423,10 @@ void HudElements::gpu_stats(){ ImGui::PopFont(); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_voltage]) { + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_voltage] && + gpu->metrics.voltage > -1 + ) { ImguiNextColumnOrNewRow(); right_aligned_text(text_color, HUDElements.ralign_width, "%i", gpu->metrics.voltage); ImGui::SameLine(0, 1.0f); @@ -647,11 +669,11 @@ void HudElements::vram(){ HUDElements.TextColored(HUDElements.colors.vram, gpu->vram_text().c_str()); ImguiNextColumnOrNewRow(); - // Add gtt_used to vram usage for APUs - if (gpu->is_apu()) - right_aligned_text(HUDElements.colors.text, HUDElements.ralign_width, "%.1f", gpu->metrics.sys_vram_used + gpu->metrics.gtt_used); - else - right_aligned_text(HUDElements.colors.text, HUDElements.ralign_width, "%.1f", gpu->metrics.sys_vram_used); + // Add shared memory usage for APUs and Intel integrated GPUs. + float vram_used = gpu->metrics.sys_vram_used; + if (gpu->is_apu() || gpu->uses_integrated_memory()) + vram_used += gpu->metrics.gtt_used; + right_aligned_text(HUDElements.colors.text, HUDElements.ralign_width, "%.1f", vram_used); if (!HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_hud_compact]){ ImGui::SameLine(0,1.0f); ImGui::PushFont(HUDElements.sw_stats->font_small); @@ -672,7 +694,10 @@ void HudElements::vram(){ HUDElements.TextColored(HUDElements.colors.text, "°C"); } - if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_mem_clock]){ + if ( + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_mem_clock] && + gpu->metrics.MemClock > 0 + ){ ImguiNextColumnOrNewRow(); right_aligned_text(HUDElements.colors.text, HUDElements.ralign_width, "%i", gpu->metrics.MemClock); ImGui::SameLine(0, 1.0f); @@ -723,7 +748,8 @@ void HudElements::proc_vram() { // show only if vram is not enabled if ( !HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_vram] && - HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_mem_temp] + HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_gpu_mem_temp] && + gpu->metrics.memory_temp > -1 ) { ImguiNextColumnOrNewRow(); diff --git a/src/meson.build b/src/meson.build index d7e4b362d0..74800c4021 100644 --- a/src/meson.build +++ b/src/meson.build @@ -263,6 +263,7 @@ if get_option('mangoapp') 'mangoapp', files( 'app/main.cpp', + 'app/frame_feed.cpp', ), c_args : [ pre_args,