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,