Update dependencies
This commit is contained in:
parent
b86302534f
commit
2b1872ae35
1803
Cargo.lock
generated
1803
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,20 @@
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use amdgpu_config::fan::{MatrixPoint, DEFAULT_FAN_CONFIG_PATH};
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode};
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use gumdrop::Options;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use std::{io, time::Duration};
|
||||
use tui::backend::Backend;
|
||||
use tui::backend::{Backend, CrosstermBackend};
|
||||
use tui::layout::*;
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::symbols::Marker;
|
||||
use tui::{backend::CrosstermBackend, layout::*, widgets::*, Frame, Terminal};
|
||||
use tui::widgets::*;
|
||||
use tui::{Frame, Terminal};
|
||||
|
||||
#[derive(Options)]
|
||||
struct Opts {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu_config::fan::Config;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::Fan;
|
||||
use crate::{AmdFanError, FanMode};
|
||||
|
@ -1,5 +1,3 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
use amdgpu::utils::{linear_map, load_temp_inputs};
|
||||
use amdgpu::{
|
||||
@ -7,6 +5,7 @@ use amdgpu::{
|
||||
PULSE_WIDTH_MODULATION_MIN, PULSE_WIDTH_MODULATION_MODE,
|
||||
};
|
||||
use amdgpu_config::fan::Config;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::{change_mode, service};
|
||||
|
||||
@ -90,7 +89,8 @@ impl Fan {
|
||||
v.into_iter().map(|hw| Self::wrap(hw, config)).collect()
|
||||
}
|
||||
|
||||
/// Change fan speed to given value if it's between minimal and maximal value
|
||||
/// Change fan speed to given value if it's between minimal and maximal
|
||||
/// value
|
||||
pub fn set_speed(&mut self, speed: f64) -> crate::Result<()> {
|
||||
let min = self.pwm_min() as f64;
|
||||
let max = self.pwm_max() as f64;
|
||||
@ -99,15 +99,16 @@ impl Fan {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change gpu fan speed management to manual (amdfand will manage speed) instead of
|
||||
/// GPU embedded manager
|
||||
/// Change gpu fan speed management to manual (amdfand will manage speed)
|
||||
/// instead of GPU embedded manager
|
||||
pub fn write_manual(&self) -> crate::Result<()> {
|
||||
self.hw_mon_write(MODULATION_ENABLED_FILE, 1)
|
||||
.map_err(FanError::ManualSpeedFailed)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change gpu fan speed management to automatic, speed will be managed by GPU embedded manager
|
||||
/// Change gpu fan speed management to automatic, speed will be managed by
|
||||
/// GPU embedded manager
|
||||
pub fn write_automatic(&self) -> crate::Result<()> {
|
||||
self.hw_mon_write("pwm1_enable", 2)
|
||||
.map_err(FanError::AutomaticSpeedFailed)?;
|
||||
@ -132,8 +133,8 @@ impl Fan {
|
||||
}
|
||||
|
||||
/// Get maximal GPU temperature from all inputs.
|
||||
/// This is not recommended since GPU can heat differently in different parts and usually only
|
||||
/// temp1 should be taken for consideration.
|
||||
/// This is not recommended since GPU can heat differently in different
|
||||
/// parts and usually only temp1 should be taken for consideration.
|
||||
pub fn max_gpu_temp(&self) -> crate::Result<f64> {
|
||||
if let Some(input) = self.temp_input.as_ref() {
|
||||
let value = self.read_gpu_temp(&input.as_string())?;
|
||||
|
@ -1,12 +1,9 @@
|
||||
extern crate log;
|
||||
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::{
|
||||
lock_file::PidLock,
|
||||
utils::{ensure_config_dir, hw_mons},
|
||||
};
|
||||
use amdgpu::lock_file::PidLock;
|
||||
use amdgpu::utils::{ensure_config_dir, hw_mons};
|
||||
use amdgpu_config::fan::{load_config, Config, DEFAULT_FAN_CONFIG_PATH};
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::FanCommand;
|
||||
use crate::error::AmdFanError;
|
||||
|
@ -1,13 +1,13 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::utils::hw_mons;
|
||||
use amdgpu::{config_reloaded, is_reload_required, listen_unix_signal};
|
||||
use amdgpu_config::fan::Config;
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::Fan;
|
||||
use crate::AmdFanError;
|
||||
|
||||
/// Start service which will change fan speed according to config and GPU temperature
|
||||
/// Start service which will change fan speed according to config and GPU
|
||||
/// temperature
|
||||
pub fn run(mut config: Config) -> crate::Result<()> {
|
||||
listen_unix_signal();
|
||||
|
||||
|
@ -28,7 +28,8 @@ impl MatrixPoint {
|
||||
pub struct Config {
|
||||
#[serde(skip)]
|
||||
path: String,
|
||||
/// One of temperature inputs /sys/class/drm/card{X}/device/hwmon/hwmon{Y}/temp{Z}_input
|
||||
/// One of temperature inputs
|
||||
/// /sys/class/drm/card{X}/device/hwmon/hwmon{Y}/temp{Z}_input
|
||||
/// If nothing is provided higher reading will be taken (this is not good!)
|
||||
temp_input: Option<TempInput>,
|
||||
log_level: LogLevel,
|
||||
@ -242,9 +243,8 @@ pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod parse_config {
|
||||
use serde::Deserialize;
|
||||
|
||||
use amdgpu::{AmdGpuError, Card, TempInput};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct Foo {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use amdgpu::utils::ensure_config;
|
||||
use amdgpu::LogLevel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub static DEFAULT_MONITOR_CONFIG_PATH: &str = "/etc/amdfand/monitor.toml";
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
#[cfg(feature = "gui-helper")]
|
||||
use crate::helper_cmd::GuiHelperError;
|
||||
use crate::lock_file::LockFileError;
|
||||
use crate::ports::PortsError;
|
||||
use pidlock::PidlockError;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use pidlock::PidlockError;
|
||||
|
||||
use crate::lock_file::LockFileError;
|
||||
#[cfg(feature = "gui-helper")]
|
||||
use crate::pidfile::helper_cmd::GuiHelperError;
|
||||
use crate::pidfile::ports::PortsError;
|
||||
|
||||
pub struct IoFailure {
|
||||
pub io: std::io::Error,
|
||||
pub path: std::path::PathBuf,
|
||||
|
@ -1,16 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use card::*;
|
||||
pub use error::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use temp_input::*;
|
||||
|
||||
mod card;
|
||||
mod error;
|
||||
#[cfg(feature = "gui-helper")]
|
||||
pub mod helper_cmd;
|
||||
pub mod hw_mon;
|
||||
pub mod lock_file;
|
||||
pub mod ports;
|
||||
pub mod pidfile;
|
||||
mod temp_input;
|
||||
pub mod utils;
|
||||
|
||||
@ -28,7 +26,9 @@ pub static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
|
||||
/// pulse width modulation fan level (0-255)
|
||||
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
|
||||
|
||||
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
|
||||
/// pulse width modulation fan control method (0: no fan speed control, 1:
|
||||
/// manual fan speed control using pwm interface, 2: automatic fan speed
|
||||
/// control)
|
||||
pub static PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
|
||||
|
||||
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";
|
||||
|
@ -1,11 +1,13 @@
|
||||
//! Create lock file and prevent running 2 identical services.
|
||||
//! NOTE: For 2 amdfand services you may just give 2 different names
|
||||
|
||||
use crate::helper_cmd::Pid;
|
||||
use crate::IoFailure;
|
||||
use nix::libc;
|
||||
use std::path::Path;
|
||||
|
||||
use nix::libc;
|
||||
|
||||
use crate::pidfile::Pid;
|
||||
use crate::IoFailure;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LockFileError {
|
||||
#[error("Failed to read {path}. {err:?}")]
|
||||
@ -56,7 +58,8 @@ impl PidLock {
|
||||
/// Create new lock file. File will be created if:
|
||||
/// * pid file does not exists
|
||||
/// * pid file exists but process is dead
|
||||
/// * old pid and current pid have different names (lock file exists after reboot and PID was taken by other process)
|
||||
/// * old pid and current pid have different names (lock file exists after
|
||||
/// reboot and PID was taken by other process)
|
||||
pub fn acquire(&mut self) -> Result<(), crate::error::AmdGpuError> {
|
||||
log::debug!("PID LOCK acquiring {}", self.pid_path);
|
||||
let pid = self.process_pid();
|
||||
@ -84,7 +87,7 @@ impl PidLock {
|
||||
log::warn!("Conflicting {} and {} for process {}", old.0, pid.0, name);
|
||||
return Err(LockFileError::Conflict { pid: old, name }.into());
|
||||
}
|
||||
Ok(name /*name isn't the same*/) => {
|
||||
Ok(name /* name isn't the same */) => {
|
||||
log::debug!(
|
||||
"Old process {:?} and current process {:?} have different names, overriding....",
|
||||
name, self.name
|
||||
|
@ -1,9 +1,10 @@
|
||||
//! AMD GUI helper communication toolkit
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use crate::pidfile::{Pid, PidResponse};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum GuiHelperError {
|
||||
#[error("GUI Helper socket file not found. Is service running?")]
|
||||
@ -14,18 +15,6 @@ pub enum GuiHelperError {
|
||||
Serialize(#[from] ron::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Pid(pub i32);
|
||||
|
||||
impl Deref for Pid {
|
||||
type Target = i32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Command {
|
||||
ReloadConfig { pid: Pid },
|
||||
@ -41,6 +30,12 @@ pub enum Response {
|
||||
ConfigFileSaveFailed(String),
|
||||
}
|
||||
|
||||
impl PidResponse for Response {
|
||||
fn kill_response() -> Self {
|
||||
Self::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sock_file() -> std::path::PathBuf {
|
||||
std::path::Path::new("/tmp").join("amdgui-helper.sock")
|
||||
}
|
133
amdgpu/src/pidfile/mod.rs
Normal file
133
amdgpu/src/pidfile/mod.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Read, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::net::Shutdown;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
pub mod helper_cmd;
|
||||
pub mod ports;
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Pid(pub i32);
|
||||
|
||||
impl Deref for Pid {
|
||||
type Target = i32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_connection<HandleCmd, Cmd, Res>(stream: UnixStream, handle_command: HandleCmd)
|
||||
where
|
||||
HandleCmd: FnOnce(Service<Res>, Cmd) + Copy,
|
||||
Cmd: DeserializeOwned + Serialize + Debug,
|
||||
Res: DeserializeOwned + Serialize + Debug + PidResponse,
|
||||
{
|
||||
let mut service = Service::<Res>::new(stream);
|
||||
|
||||
let command = match service.read_command() {
|
||||
Some(s) => s,
|
||||
_ => return service.kill(),
|
||||
};
|
||||
|
||||
log::info!("Incoming {:?}", command);
|
||||
let cmd = match ron::from_str::<Cmd>(command.trim()) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => {
|
||||
log::warn!("Invalid message {:?}. {:?}", command, e);
|
||||
return service.kill();
|
||||
}
|
||||
};
|
||||
handle_command(service, cmd);
|
||||
}
|
||||
|
||||
pub trait PidResponse: Sized {
|
||||
fn kill_response() -> Self;
|
||||
}
|
||||
|
||||
pub struct Service<Response>(UnixStream, PhantomData<Response>)
|
||||
where
|
||||
Response: serde::Serialize + Debug + PidResponse;
|
||||
|
||||
impl<Response> Service<Response>
|
||||
where
|
||||
Response: serde::Serialize + Debug + PidResponse,
|
||||
{
|
||||
pub fn new(file: UnixStream) -> Self {
|
||||
Self(file, Default::default())
|
||||
}
|
||||
|
||||
/// Serialize and send command
|
||||
pub fn write_response(&mut self, res: Response) {
|
||||
write_response(&mut self.0, res)
|
||||
}
|
||||
|
||||
/// Read from `.sock` file new line separated commands
|
||||
pub fn read_command(&mut self) -> Option<String> {
|
||||
read_command(&mut self.0)
|
||||
}
|
||||
|
||||
/// Close connection with no operation response
|
||||
pub fn kill(mut self) {
|
||||
self.write_response(Response::kill_response());
|
||||
self.close();
|
||||
}
|
||||
|
||||
pub fn close(self) {
|
||||
let _ = self.0.shutdown(Shutdown::Both);
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize and send command
|
||||
pub fn write_response<Response>(file: &mut UnixStream, res: Response)
|
||||
where
|
||||
Response: serde::Serialize + Debug,
|
||||
{
|
||||
match ron::to_string(&res) {
|
||||
Ok(buffer) => match file.write_all(buffer.as_bytes()) {
|
||||
Ok(_) => {
|
||||
log::info!("Response successfully written")
|
||||
}
|
||||
Err(e) => log::warn!("Failed to write response. {:?}", e),
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from `.sock` file new line separated commands
|
||||
pub fn read_command(file: &mut UnixStream) -> Option<String> {
|
||||
let mut command = String::with_capacity(100);
|
||||
log::info!("Reading stream...");
|
||||
read_line(file, &mut command);
|
||||
if command.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(command)
|
||||
}
|
||||
|
||||
pub fn read_line(stream: &mut UnixStream, command: &mut String) {
|
||||
let mut buffer = [0];
|
||||
while stream.read_exact(&mut buffer).is_ok() {
|
||||
if buffer[0] == b'\n' {
|
||||
break;
|
||||
}
|
||||
match std::str::from_utf8(&buffer) {
|
||||
Ok(s) => {
|
||||
command.push_str(s);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read from client. {:?}", e);
|
||||
let _ = stream.shutdown(Shutdown::Both);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
//! AMD GUI helper communication toolkit
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::pidfile::PidResponse;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PortsError {
|
||||
#[error("AMD GPU ports socket file not found. Is service running?")]
|
||||
@ -74,18 +75,6 @@ impl Output {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Pid(pub i32);
|
||||
|
||||
impl Deref for Pid {
|
||||
type Target = i32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Command {
|
||||
Ports,
|
||||
@ -97,6 +86,12 @@ pub enum Response {
|
||||
NoOp,
|
||||
}
|
||||
|
||||
impl PidResponse for Response {
|
||||
fn kill_response() -> Self {
|
||||
Self::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sock_file() -> PathBuf {
|
||||
std::path::Path::new("/tmp").join("amdgpu-ports.sock")
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use crate::AmdGpuError;
|
||||
use serde::Serializer;
|
||||
|
||||
use crate::AmdGpuError;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub struct TempInput(pub u16);
|
||||
|
||||
|
@ -1,23 +1,26 @@
|
||||
//! Special daemon with root privileges. Since GUI should not have (and sometimes can't have) root
|
||||
//! privileges and service processes are designed to be as small as possible this is proxy.
|
||||
//! Special daemon with root privileges. Since GUI should not have (and
|
||||
//! sometimes can't have) root privileges and service processes are designed to
|
||||
//! be as small as possible this is proxy.
|
||||
//!
|
||||
//! It is responsible for:
|
||||
//! * Loading all amdfand processes. In order to do this process needs to be killed with signal 0 to check if it still is alive
|
||||
//! * Loading all amdfand processes. In order to do this process needs to be
|
||||
//! killed with signal 0 to check if it still is alive
|
||||
//! * Reload amdfand process with signal SIGHUP
|
||||
//! * Save changed config file
|
||||
//!
|
||||
//! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for communication.
|
||||
//! After each operation connection is terminated so each command needs new connection.
|
||||
//! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for
|
||||
//! communication. After each operation connection is terminated so each command
|
||||
//! needs new connection.
|
||||
#![allow(clippy::non_octal_unix_permissions)]
|
||||
|
||||
use amdgpu::helper_cmd::{Command, Pid, Response};
|
||||
use amdgpu::IoFailure;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::Permissions;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::os::unix::net::UnixListener;
|
||||
|
||||
use amdgpu::pidfile::helper_cmd::{Command, Response};
|
||||
use amdgpu::pidfile::{handle_connection, Pid};
|
||||
use amdgpu::IoFailure;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@ -38,7 +41,7 @@ fn main() -> Result<()> {
|
||||
let mut lock = amdgpu::lock_file::PidLock::new("amdgui", String::from("helper"))?;
|
||||
lock.acquire()?;
|
||||
|
||||
let sock_path = amdgpu::helper_cmd::sock_file();
|
||||
let sock_path = amdgpu::pidfile::helper_cmd::sock_file();
|
||||
let listener = {
|
||||
let _ = std::fs::remove_file(&sock_path);
|
||||
|
||||
@ -52,86 +55,27 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
while let Ok((stream, _addr)) = listener.accept() {
|
||||
handle_connection(stream);
|
||||
handle_connection::<_, Command, Response>(stream, handle_command);
|
||||
}
|
||||
|
||||
lock.release()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Service(UnixStream);
|
||||
pub type Service = amdgpu::pidfile::Service<Response>;
|
||||
|
||||
impl Service {
|
||||
/// Serialize and send command
|
||||
pub fn write_response(&mut self, res: amdgpu::helper_cmd::Response) {
|
||||
match ron::to_string(&res) {
|
||||
Ok(buffer) => match self.0.write_all(buffer.as_bytes()) {
|
||||
Ok(_) => {
|
||||
log::info!("Response successfully written")
|
||||
}
|
||||
Err(e) => log::warn!("Failed to write response. {:?}", e),
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from `.sock` file new line separated commands
|
||||
pub fn read_command(&mut self) -> Option<String> {
|
||||
let mut command = String::with_capacity(100);
|
||||
log::info!("Reading stream...");
|
||||
read_line(&mut self.0, &mut command);
|
||||
if command.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(command)
|
||||
}
|
||||
|
||||
/// Close connection with no operation response
|
||||
pub fn kill(mut self) {
|
||||
self.write_response(Response::NoOp);
|
||||
self.close();
|
||||
}
|
||||
|
||||
pub fn close(self) {
|
||||
let _ = self.0.shutdown(Shutdown::Both);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(stream: UnixStream) {
|
||||
let mut service = Service(stream);
|
||||
|
||||
let command = match service.read_command() {
|
||||
Some(s) => s,
|
||||
_ => return service.kill(),
|
||||
};
|
||||
|
||||
log::info!("Incoming {:?}", command);
|
||||
let cmd = match ron::from_str::<amdgpu::helper_cmd::Command>(command.trim()) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(e) => {
|
||||
log::warn!("Invalid message {:?}. {:?}", command, e);
|
||||
return service.kill();
|
||||
}
|
||||
};
|
||||
handle_command(service, cmd);
|
||||
}
|
||||
|
||||
fn handle_command(mut service: Service, cmd: Command) {
|
||||
fn handle_command(service: Service, cmd: Command) {
|
||||
match cmd {
|
||||
Command::ReloadConfig { pid } => {
|
||||
log::info!("Reloading config file for pid {:?}", pid);
|
||||
handle_reload_config(service, pid);
|
||||
}
|
||||
Command::FanServices => handle_fan_services(service),
|
||||
Command::SaveFanConfig { path, content } => {
|
||||
handle_save_fan_config(&mut service, path, content)
|
||||
}
|
||||
Command::SaveFanConfig { path, content } => handle_save_fan_config(service, path, content),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_save_fan_config(service: &mut Service, path: String, content: String) {
|
||||
fn handle_save_fan_config(mut service: Service, path: String, content: String) {
|
||||
match std::fs::write(path, content) {
|
||||
Err(e) => service.write_response(Response::ConfigFileSaveFailed(format!("{:?}", e))),
|
||||
Ok(..) => service.write_response(Response::ConfigFileSaved),
|
||||
@ -145,25 +89,6 @@ fn handle_fan_services(mut service: Service) {
|
||||
service.write_response(Response::Services(services));
|
||||
}
|
||||
|
||||
fn read_line(stream: &mut UnixStream, command: &mut String) {
|
||||
let mut buffer = [0];
|
||||
while stream.read_exact(&mut buffer).is_ok() {
|
||||
if buffer[0] == b'\n' {
|
||||
break;
|
||||
}
|
||||
match std::str::from_utf8(&buffer) {
|
||||
Ok(s) => {
|
||||
command.push_str(s);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read from client. {:?}", e);
|
||||
let _ = stream.shutdown(Shutdown::Both);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_reload_config(service: Service, pid: Pid) {
|
||||
unsafe {
|
||||
nix::libc::kill(pid.0, nix::sys::signal::Signal::SIGHUP as i32);
|
||||
|
@ -40,28 +40,31 @@ gumdrop = { version = "0.8" }
|
||||
log = { version = "0.4" }
|
||||
pretty_env_logger = { version = "0.4" }
|
||||
|
||||
egui = { version = "0.15", optional = true }
|
||||
epaint = { version = "0.15", features = ["serialize"], optional = true }
|
||||
epi = { version = "0.15", optional = true }
|
||||
winit = { version = "0.25", optional = true }
|
||||
egui-winit = { version = "0.15", optional = true }
|
||||
egui = { version = "0.18", optional = true, features = [] }
|
||||
epaint = { version = "0.18", features = [], optional = true }
|
||||
epi = { version = "0.17.0", optional = true }
|
||||
winit = { version = "0.26", optional = true }
|
||||
egui-winit = { version = "0.18", optional = true }
|
||||
# vulkan
|
||||
egui_vulkano = { version = "0.4", optional = true }
|
||||
vulkano-win = { version = "0.25", optional = true }
|
||||
vulkano = { version = "0.25", optional = true }
|
||||
vulkano-shaders = { version = "0.25", optional = true }
|
||||
egui_vulkano = { version = "0.8.0", optional = true }
|
||||
vulkano-win = { version = "0.29.0", optional = true }
|
||||
vulkano = { version = "0.29.0", optional = true }
|
||||
vulkano-shaders = { version = "0.29.0", optional = true }
|
||||
bytemuck = { version = "*" }
|
||||
# xorg glium
|
||||
glium = { version = "0.30", optional = true }
|
||||
egui_glium = { version = "0.15", optional = true }
|
||||
glium = { version = "0.32.1", optional = true }
|
||||
egui_glium = { version = "0.18.0", optional = true }
|
||||
# xorg glow
|
||||
glutin = { version = "0.27", optional = true }
|
||||
glutin = { version = "0.29", optional = true }
|
||||
glow = { version = "0.11", optional = true }
|
||||
egui_glow = { version = "0.15", optional = true }
|
||||
egui_glow = { version = "0.18", optional = true }
|
||||
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
parking_lot = { version = "0.11" }
|
||||
parking_lot = { version = "0.12" }
|
||||
|
||||
nix = { version = "0.24" }
|
||||
nix = { version = "0.25" }
|
||||
|
||||
image = { version = "0.24.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
amdgpu = { path = "../amdgpu", version = "1.0", features = ["gui-helper"] }
|
||||
|
BIN
amdguid/assets/icons/html_port-512.png
Normal file
BIN
amdguid/assets/icons/html_port-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
BIN
amdguid/assets/icons/ports.jpg
Normal file
BIN
amdguid/assets/icons/ports.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
BIN
amdguid/assets/icons/vga_port-512.png
Normal file
BIN
amdguid/assets/icons/vga_port-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
@ -1,10 +1,14 @@
|
||||
use crate::widgets::{ChangeFanSettings, CoolingPerformance};
|
||||
use amdgpu::helper_cmd::Pid;
|
||||
use egui::{CtxRef, Ui};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::Arc;
|
||||
|
||||
use amdgpu::pidfile::ports::Output;
|
||||
use amdgpu::pidfile::Pid;
|
||||
use egui::Ui;
|
||||
use epi::Frame;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::widgets::outputs_settings::OutputsSettings;
|
||||
use crate::widgets::{ChangeFanSettings, CoolingPerformance};
|
||||
|
||||
pub enum ChangeState {
|
||||
New,
|
||||
@ -61,6 +65,7 @@ impl From<Vec<Pid>> for FanServices {
|
||||
pub enum Page {
|
||||
Config,
|
||||
Monitoring,
|
||||
Outputs,
|
||||
Settings,
|
||||
}
|
||||
|
||||
@ -85,14 +90,16 @@ pub struct StatefulConfig {
|
||||
pub struct AmdGui {
|
||||
pub page: Page,
|
||||
pid_files: FanServices,
|
||||
outputs: BTreeMap<String, Vec<Output>>,
|
||||
cooling_performance: CoolingPerformance,
|
||||
change_fan_settings: ChangeFanSettings,
|
||||
outputs_settings: OutputsSettings,
|
||||
config: StatefulConfig,
|
||||
reload_pid_list_delay: u8,
|
||||
}
|
||||
|
||||
impl epi::App for AmdGui {
|
||||
fn update(&mut self, _ctx: &CtxRef, _frame: &mut Frame<'_>) {}
|
||||
fn update(&mut self, _ctx: &epi::egui::Context, _frame: &Frame) {}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"AMD GUI"
|
||||
@ -104,8 +111,10 @@ impl AmdGui {
|
||||
Self {
|
||||
page: Default::default(),
|
||||
pid_files: FanServices::from(vec![]),
|
||||
outputs: Default::default(),
|
||||
cooling_performance: CoolingPerformance::new(100, config.clone()),
|
||||
change_fan_settings: ChangeFanSettings::new(config.clone()),
|
||||
outputs_settings: OutputsSettings::default(),
|
||||
config: StatefulConfig {
|
||||
config,
|
||||
state: ChangeState::New,
|
||||
@ -124,6 +133,10 @@ impl AmdGui {
|
||||
self.cooling_performance.draw(ui, &self.pid_files);
|
||||
}
|
||||
Page::Settings => {}
|
||||
Page::Outputs => {
|
||||
self.outputs_settings
|
||||
.draw(ui, &mut self.config, &self.outputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,20 +144,71 @@ impl AmdGui {
|
||||
self.cooling_performance.tick();
|
||||
if self.pid_files.0.is_empty() || self.reload_pid_list_delay.checked_sub(1).is_none() {
|
||||
self.reload_pid_list_delay = RELOAD_PID_LIST_DELAY;
|
||||
match amdgpu::helper_cmd::send_command(amdgpu::helper_cmd::Command::FanServices) {
|
||||
Ok(amdgpu::helper_cmd::Response::Services(services))
|
||||
if self.pid_files.list_changed(&services) =>
|
||||
{
|
||||
self.pid_files = FanServices::from(services);
|
||||
|
||||
{
|
||||
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
|
||||
|
||||
match send_command(Command::FanServices) {
|
||||
Ok(Response::Services(services)) if self.pid_files.list_changed(&services) => {
|
||||
self.pid_files = FanServices::from(services);
|
||||
}
|
||||
Ok(Response::Services(_services)) => {
|
||||
// SKIP
|
||||
}
|
||||
Ok(res) => {
|
||||
log::warn!("Unexpected response {:?} while loading fan services", res);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load amd fan services pid list. {:?}", e);
|
||||
}
|
||||
}
|
||||
Ok(amdgpu::helper_cmd::Response::Services(_services)) => {
|
||||
// SKIP
|
||||
}
|
||||
Ok(res) => {
|
||||
log::warn!("Unexpected response {:?} while loading fan services", res);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load amd fan services pid list. {:?}", e);
|
||||
}
|
||||
|
||||
{
|
||||
use amdgpu::pidfile::ports::{send_command, Command, Response};
|
||||
|
||||
match send_command(Command::Ports) {
|
||||
Ok(Response::NoOp) => {}
|
||||
Ok(Response::Ports(outputs)) => {
|
||||
let mut names = outputs.iter().fold(
|
||||
Vec::with_capacity(outputs.len()),
|
||||
|mut set, output| {
|
||||
set.push(format!("{}", output.card));
|
||||
set
|
||||
},
|
||||
);
|
||||
names.sort();
|
||||
|
||||
let mut tree = BTreeMap::new();
|
||||
names.into_iter().for_each(|name| {
|
||||
tree.insert(name, Vec::with_capacity(6));
|
||||
});
|
||||
|
||||
self.outputs = outputs.into_iter().fold(tree, |mut agg, output| {
|
||||
let v = agg
|
||||
.entry(output.card.clone())
|
||||
.or_insert_with(|| Vec::with_capacity(6));
|
||||
v.push(output);
|
||||
v.sort_by(|a, b| {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
a.port_type,
|
||||
a.port_name.as_deref().unwrap_or_default(),
|
||||
a.port_number,
|
||||
)
|
||||
.cmp(&format!(
|
||||
"{}{}{}",
|
||||
b.port_type,
|
||||
b.port_name.as_deref().unwrap_or_default(),
|
||||
b.port_number,
|
||||
))
|
||||
});
|
||||
agg
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load amd fan services pid list. {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1,10 +1,11 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use glium::glutin;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
|
||||
use crate::app::AmdGui;
|
||||
use crate::backend::create_ui;
|
||||
use glium::glutin;
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
let window_builder = glutin::window::WindowBuilder::new()
|
||||
|
@ -108,7 +108,9 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, mut receiver: UnboundedReceiver<bool
|
||||
|
||||
egui.on_event(&event);
|
||||
|
||||
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
|
||||
gl_window.window().request_redraw(); // TODO: ask egui if the
|
||||
// events warrants a
|
||||
// repaint instead
|
||||
}
|
||||
glutin::event::Event::LoopDestroyed => {
|
||||
egui.destroy(&gl);
|
||||
|
@ -5,39 +5,52 @@ pub mod glow_backend;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod wayland_backend;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::panel::TopBottomSide;
|
||||
use egui::{Layout, PointerButton};
|
||||
use epaint::TextStyle;
|
||||
#[cfg(feature = "xorg-glium")]
|
||||
pub use glium_backend::run_app;
|
||||
#[cfg(feature = "xorg-glow")]
|
||||
pub use glow_backend::run_app;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use wayland_backend::run_app;
|
||||
|
||||
#[cfg(feature = "xorg-glow")]
|
||||
pub use glow_backend::run_app;
|
||||
|
||||
use crate::app::Page;
|
||||
use crate::AmdGui;
|
||||
#[cfg(feature = "xorg-glium")]
|
||||
pub use glium_backend::run_app;
|
||||
|
||||
pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::CtxRef) {
|
||||
pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::Context) {
|
||||
egui::containers::TopBottomPanel::new(TopBottomSide::Top, "menu").show(ctx, |ui| {
|
||||
let mut child = ui.child_ui(ui.available_rect_before_wrap(), Layout::left_to_right());
|
||||
if child
|
||||
.add(egui::Button::new("Config").text_style(TextStyle::Heading))
|
||||
.add(
|
||||
egui::Button::new("Outputs"), /* .text_style(TextStyle::Heading) */
|
||||
)
|
||||
.clicked_by(PointerButton::Primary)
|
||||
{
|
||||
amd_gui.lock().page = Page::Outputs;
|
||||
}
|
||||
if child
|
||||
.add(
|
||||
egui::Button::new("Config"), /* .text_style(TextStyle::Heading) */
|
||||
)
|
||||
.clicked_by(PointerButton::Primary)
|
||||
{
|
||||
amd_gui.lock().page = Page::Config;
|
||||
}
|
||||
if child
|
||||
.add(egui::Button::new("Monitoring").text_style(TextStyle::Heading))
|
||||
.add(
|
||||
egui::Button::new("Monitoring"), /* .text_style(TextStyle::Heading) */
|
||||
)
|
||||
.clicked_by(PointerButton::Primary)
|
||||
{
|
||||
amd_gui.lock().page = Page::Monitoring;
|
||||
}
|
||||
if child
|
||||
.add(egui::Button::new("Settings").text_style(TextStyle::Heading))
|
||||
.add(
|
||||
egui::Button::new("Settings"), /* .text_style(TextStyle::Heading) */
|
||||
)
|
||||
.clicked_by(PointerButton::Primary)
|
||||
{
|
||||
amd_gui.lock().page = Page::Settings;
|
||||
@ -54,6 +67,9 @@ pub fn create_ui(amd_gui: Arc<Mutex<AmdGui>>, ctx: &egui::CtxRef) {
|
||||
Page::Monitoring => {
|
||||
gui.ui(ui);
|
||||
}
|
||||
Page::Outputs => {
|
||||
gui.ui(ui);
|
||||
}
|
||||
Page::Settings => {
|
||||
ctx.settings_ui(ui);
|
||||
}
|
||||
|
@ -1,112 +1,156 @@
|
||||
use crate::app::AmdGui;
|
||||
use crate::backend::create_ui;
|
||||
use parking_lot::Mutex;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use egui_vulkano::UpdateTexturesResult;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
|
||||
use vulkano::command_buffer::{
|
||||
AutoCommandBufferBuilder, CommandBufferUsage, DynamicState, SubpassContents,
|
||||
};
|
||||
use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer, TypedBufferAccess};
|
||||
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, SubpassContents};
|
||||
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
||||
use vulkano::device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo};
|
||||
use vulkano::format::Format;
|
||||
use vulkano::image::view::ImageView;
|
||||
use vulkano::image::{ImageUsage, SwapchainImage};
|
||||
use vulkano::render_pass::{Framebuffer, FramebufferAbstract, RenderPass, Subpass};
|
||||
use vulkano::swapchain::{AcquireError, ColorSpace, Swapchain, SwapchainCreationError};
|
||||
use vulkano::sync::{FlushError, GpuFuture};
|
||||
use vulkano::{swapchain, sync, Version};
|
||||
use vulkano::image::{ImageAccess, ImageUsage, SwapchainImage};
|
||||
use vulkano::instance::{Instance, InstanceCreateInfo};
|
||||
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
|
||||
use vulkano::pipeline::graphics::vertex_input::BuffersDefinition;
|
||||
use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState};
|
||||
use vulkano::pipeline::GraphicsPipeline;
|
||||
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass};
|
||||
use vulkano::swapchain::{AcquireError, Swapchain, SwapchainCreateInfo, SwapchainCreationError};
|
||||
use vulkano::sync::{FenceSignalFuture, FlushError, GpuFuture};
|
||||
use vulkano::{swapchain, sync};
|
||||
use vulkano_win::VkSurfaceBuild;
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::ControlFlow;
|
||||
use winit::window::Window;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::{Fullscreen, Window, WindowBuilder};
|
||||
|
||||
pub mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
src: "
|
||||
#version 450
|
||||
layout(location = 0) in vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
"
|
||||
}
|
||||
}
|
||||
use crate::app::AmdGui;
|
||||
use crate::backend::create_ui;
|
||||
|
||||
pub mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
src: "
|
||||
#version 450
|
||||
layout(location = 0) out vec4 f_color;
|
||||
void main() {
|
||||
f_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
|
||||
struct Vertex {
|
||||
position: [f32; 2],
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
pub enum FrameEndFuture<F: GpuFuture + 'static> {
|
||||
FenceSignalFuture(FenceSignalFuture<F>),
|
||||
BoxedFuture(Box<dyn GpuFuture>),
|
||||
}
|
||||
|
||||
impl<F: GpuFuture> FrameEndFuture<F> {
|
||||
pub fn now(device: Arc<Device>) -> Self {
|
||||
Self::BoxedFuture(sync::now(device).boxed())
|
||||
}
|
||||
|
||||
pub fn get(self) -> Box<dyn GpuFuture> {
|
||||
match self {
|
||||
FrameEndFuture::FenceSignalFuture(f) => f.boxed(),
|
||||
FrameEndFuture::BoxedFuture(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: GpuFuture> AsMut<dyn GpuFuture> for FrameEndFuture<F> {
|
||||
fn as_mut(&mut self) -> &mut (dyn GpuFuture + 'static) {
|
||||
match self {
|
||||
FrameEndFuture::FenceSignalFuture(f) => f,
|
||||
FrameEndFuture::BoxedFuture(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>) {
|
||||
let required_extensions = vulkano_win::required_extensions();
|
||||
let instance =
|
||||
vulkano::instance::Instance::new(None, Version::V1_0, &required_extensions, None).unwrap();
|
||||
let physical = vulkano::device::physical::PhysicalDevice::enumerate(&instance)
|
||||
.next()
|
||||
.unwrap();
|
||||
let instance = Instance::new(InstanceCreateInfo {
|
||||
enabled_extensions: required_extensions,
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let event_loop = winit::event_loop::EventLoop::new();
|
||||
let surface = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(1024, 768))
|
||||
.with_title("AMD GUI")
|
||||
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
|
||||
|
||||
println!(
|
||||
"Using device: {} (type: {:?})",
|
||||
physical.properties().device_name,
|
||||
physical.properties().device_type,
|
||||
);
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let surface = WindowBuilder::new()
|
||||
.with_title("egui_vulkano demo")
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(None)))
|
||||
.build_vk_surface(&event_loop, instance.clone())
|
||||
.unwrap();
|
||||
|
||||
// vulkan
|
||||
let queue_family = physical
|
||||
.queue_families()
|
||||
.find(|&q| q.supports_graphics() && surface.is_supported(q).unwrap_or(false))
|
||||
let device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
..DeviceExtensions::none()
|
||||
};
|
||||
|
||||
let (physical_device, queue_family) = PhysicalDevice::enumerate(&instance)
|
||||
.filter(|&p| p.supported_extensions().is_superset_of(&device_extensions))
|
||||
.filter_map(|p| {
|
||||
p.queue_families()
|
||||
.find(|&q| q.supports_graphics() && q.supports_surface(&surface).unwrap_or(false))
|
||||
.map(|q| (p, q))
|
||||
})
|
||||
.min_by_key(|(p, _)| match p.properties().device_type {
|
||||
PhysicalDeviceType::DiscreteGpu => 0,
|
||||
PhysicalDeviceType::IntegratedGpu => 1,
|
||||
PhysicalDeviceType::VirtualGpu => 2,
|
||||
PhysicalDeviceType::Cpu => 3,
|
||||
PhysicalDeviceType::Other => 4,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let device_ext = vulkano::device::DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
..vulkano::device::DeviceExtensions::none()
|
||||
};
|
||||
let (device, mut queues) = vulkano::device::Device::new(
|
||||
physical,
|
||||
physical.supported_features(),
|
||||
&device_ext,
|
||||
[(queue_family, 0.5)].iter().cloned(),
|
||||
let (device, mut queues) = Device::new(
|
||||
physical_device,
|
||||
DeviceCreateInfo {
|
||||
enabled_extensions: physical_device
|
||||
.required_extensions()
|
||||
.union(&device_extensions),
|
||||
queue_create_infos: vec![QueueCreateInfo::family(queue_family)],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let queue = queues.next().unwrap();
|
||||
|
||||
let (mut swapchain, images) = {
|
||||
let caps = surface.capabilities(physical).unwrap();
|
||||
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
|
||||
let caps = physical_device
|
||||
.surface_capabilities(&surface, Default::default())
|
||||
.unwrap();
|
||||
let composite_alpha = caps.supported_composite_alpha.iter().next().unwrap();
|
||||
|
||||
assert!(&caps
|
||||
.supported_formats
|
||||
.contains(&(Format::B8G8R8A8Srgb, ColorSpace::SrgbNonLinear)));
|
||||
let format = Format::B8G8R8A8Srgb;
|
||||
let dimensions: [u32; 2] = surface.window().inner_size().into();
|
||||
let image_format = Some(Format::B8G8R8A8_SRGB);
|
||||
let image_extent: [u32; 2] = surface.window().inner_size().into();
|
||||
|
||||
Swapchain::start(device.clone(), surface.clone())
|
||||
.num_images(caps.min_image_count)
|
||||
.format(format)
|
||||
.dimensions(dimensions)
|
||||
.usage(ImageUsage::color_attachment())
|
||||
.sharing_mode(&queue)
|
||||
.composite_alpha(alpha)
|
||||
.build()
|
||||
.unwrap()
|
||||
Swapchain::new(
|
||||
device.clone(),
|
||||
surface.clone(),
|
||||
SwapchainCreateInfo {
|
||||
min_image_count: caps.min_image_count,
|
||||
image_format,
|
||||
image_extent,
|
||||
image_usage: ImageUsage::color_attachment(),
|
||||
composite_alpha,
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
struct Vertex {
|
||||
position: [f32; 2],
|
||||
}
|
||||
vulkano::impl_vertex!(Vertex, position);
|
||||
|
||||
let vertex_buffer = {
|
||||
@ -131,59 +175,82 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let vs = vs::Shader::load(device.clone()).unwrap();
|
||||
let fs = fs::Shader::load(device.clone()).unwrap();
|
||||
mod vs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "vertex",
|
||||
src: "
|
||||
#version 450
|
||||
|
||||
let render_pass = Arc::new(
|
||||
vulkano::ordered_passes_renderpass!(
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
load: Clear,
|
||||
store: Store,
|
||||
format: swapchain.format(),
|
||||
samples: 1,
|
||||
}
|
||||
},
|
||||
passes: [
|
||||
{ color: [color], depth_stencil: {}, input: [] },
|
||||
{ color: [color], depth_stencil: {}, input: [] } // Create a second render pass to draw egui
|
||||
]
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
layout(location = 0) in vec2 position;
|
||||
|
||||
let pipeline = Arc::new(
|
||||
vulkano::pipeline::GraphicsPipeline::start()
|
||||
.vertex_input_single_buffer::<Vertex>()
|
||||
.vertex_shader(vs.main_entry_point(), ())
|
||||
.triangle_list()
|
||||
.viewports_dynamic_scissors_irrelevant(1)
|
||||
.fragment_shader(fs.main_entry_point(), ())
|
||||
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
|
||||
.build(device.clone())
|
||||
.unwrap(),
|
||||
);
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
"
|
||||
}
|
||||
}
|
||||
|
||||
let mut dynamic_state = DynamicState {
|
||||
line_width: None,
|
||||
viewports: None,
|
||||
scissors: None,
|
||||
compare_mask: None,
|
||||
write_mask: None,
|
||||
reference: None,
|
||||
mod fs {
|
||||
vulkano_shaders::shader! {
|
||||
ty: "fragment",
|
||||
src: "
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
"
|
||||
}
|
||||
}
|
||||
|
||||
let vs = vs::load(device.clone()).unwrap();
|
||||
let fs = fs::load(device.clone()).unwrap();
|
||||
|
||||
let render_pass = vulkano::ordered_passes_renderpass!(
|
||||
device.clone(),
|
||||
attachments: {
|
||||
color: {
|
||||
load: Clear,
|
||||
store: Store,
|
||||
format: swapchain.image_format(),
|
||||
samples: 1,
|
||||
}
|
||||
},
|
||||
passes: [
|
||||
{ color: [color], depth_stencil: {}, input: [] },
|
||||
{ color: [color], depth_stencil: {}, input: [] } // Create a second renderpass to draw egui
|
||||
]
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let pipeline = GraphicsPipeline::start()
|
||||
.vertex_input_state(BuffersDefinition::new().vertex::<Vertex>())
|
||||
.vertex_shader(vs.entry_point("main").unwrap(), ())
|
||||
.input_assembly_state(InputAssemblyState::new())
|
||||
.viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
|
||||
.fragment_shader(fs.entry_point("main").unwrap(), ())
|
||||
.render_pass(Subpass::from(render_pass.clone().into(), 0).unwrap())
|
||||
.build(device.clone())
|
||||
.unwrap();
|
||||
|
||||
let mut viewport = Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
dimensions: [0.0, 0.0],
|
||||
depth_range: 0.0..1.0,
|
||||
};
|
||||
|
||||
let mut framebuffers =
|
||||
window_size_dependent_setup(&images, render_pass.clone(), &mut dynamic_state);
|
||||
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut viewport);
|
||||
|
||||
let mut recreate_swap_chain = false;
|
||||
let mut recreate_swapchain = false;
|
||||
|
||||
let mut previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||
let mut previous_frame_end = Some(FrameEndFuture::now(device.clone()));
|
||||
|
||||
//Set up everything need to draw the gui
|
||||
let window = surface.window();
|
||||
let mut egui_ctx = egui::CtxRef::default();
|
||||
let mut egui_winit = egui_winit::State::new(window);
|
||||
let egui_ctx = egui::Context::default();
|
||||
let mut egui_winit = egui_winit::State::new(4096, window);
|
||||
|
||||
let mut egui_painter = egui_vulkano::Painter::new(
|
||||
device.clone(),
|
||||
@ -192,6 +259,11 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//Set up some window to look at for the test
|
||||
|
||||
// let mut my_texture = egui_ctx.load_texture("my_texture",
|
||||
// ColorImage::example());
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
@ -204,7 +276,7 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
event: WindowEvent::Resized(_),
|
||||
..
|
||||
} => {
|
||||
recreate_swap_chain = true;
|
||||
recreate_swapchain = true;
|
||||
}
|
||||
Event::WindowEvent { event, .. } => {
|
||||
let egui_consumed_event = egui_winit.on_event(&egui_ctx, &event);
|
||||
@ -212,39 +284,47 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
// do your own event handling here
|
||||
};
|
||||
}
|
||||
Event::UserEvent(_) | Event::RedrawEventsCleared => {
|
||||
previous_frame_end.as_mut().unwrap().cleanup_finished();
|
||||
Event::RedrawEventsCleared => {
|
||||
previous_frame_end
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.cleanup_finished();
|
||||
|
||||
if recreate_swap_chain {
|
||||
if recreate_swapchain {
|
||||
let dimensions: [u32; 2] = surface.window().inner_size().into();
|
||||
let (new_swap_chain, new_images) =
|
||||
match swapchain.recreate().dimensions(dimensions).build() {
|
||||
let (new_swapchain, new_images) =
|
||||
match swapchain.recreate(SwapchainCreateInfo {
|
||||
image_extent: surface.window().inner_size().into(),
|
||||
..swapchain.create_info()
|
||||
}) {
|
||||
Ok(r) => r,
|
||||
Err(SwapchainCreationError::UnsupportedDimensions) => return,
|
||||
Err(e) => panic!("Failed to recreate swap chain: {:?}", e),
|
||||
Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return,
|
||||
Err(e) => panic!("Failed to recreate swapchain: {:?}", e),
|
||||
};
|
||||
|
||||
swapchain = new_swap_chain;
|
||||
swapchain = new_swapchain;
|
||||
framebuffers = window_size_dependent_setup(
|
||||
&new_images,
|
||||
render_pass.clone(),
|
||||
&mut dynamic_state,
|
||||
&mut viewport,
|
||||
);
|
||||
recreate_swap_chain = false;
|
||||
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
|
||||
recreate_swapchain = false;
|
||||
}
|
||||
|
||||
let (image_num, suboptimal, acquire_future) =
|
||||
match swapchain::acquire_next_image(swapchain.clone(), None) {
|
||||
Ok(r) => r,
|
||||
Err(AcquireError::OutOfDate) => {
|
||||
recreate_swap_chain = true;
|
||||
recreate_swapchain = true;
|
||||
return;
|
||||
}
|
||||
Err(e) => panic!("Failed to acquire next image: {:?}", e),
|
||||
};
|
||||
|
||||
if suboptimal {
|
||||
recreate_swap_chain = true;
|
||||
recreate_swapchain = true;
|
||||
}
|
||||
|
||||
let clear_values = vec![[0.0, 0.0, 1.0, 1.0].into()];
|
||||
@ -255,6 +335,20 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
egui_ctx.begin_frame(egui_winit.take_egui_input(surface.window()));
|
||||
|
||||
create_ui(amd_gui.clone(), &egui_ctx);
|
||||
|
||||
let egui_output = egui_ctx.end_frame();
|
||||
let platform_output = egui_output.platform_output;
|
||||
egui_winit.handle_platform_output(surface.window(), &egui_ctx, platform_output);
|
||||
|
||||
let result = egui_painter
|
||||
.update_textures(egui_output.textures_delta, &mut builder)
|
||||
.expect("egui texture error");
|
||||
|
||||
let wait_for_last_frame = result == UpdateTexturesResult::Changed;
|
||||
|
||||
// Do your usual rendering
|
||||
builder
|
||||
.begin_render_pass(
|
||||
@ -263,29 +357,23 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
clear_values,
|
||||
)
|
||||
.unwrap()
|
||||
.draw(
|
||||
pipeline.clone(),
|
||||
&dynamic_state,
|
||||
vertex_buffer.clone(),
|
||||
(),
|
||||
(),
|
||||
)
|
||||
.set_viewport(0, [viewport.clone()])
|
||||
.bind_pipeline_graphics(pipeline.clone())
|
||||
.bind_vertex_buffers(0, vertex_buffer.clone())
|
||||
.draw(vertex_buffer.len().try_into().unwrap(), 1, 0, 0)
|
||||
.unwrap(); // Don't end the render pass yet
|
||||
|
||||
egui_ctx.begin_frame(egui_winit.take_egui_input(surface.window()));
|
||||
// Build your gui
|
||||
|
||||
create_ui(amd_gui.clone(), &egui_ctx);
|
||||
|
||||
let (egui_output, clipped_shapes) = egui_ctx.end_frame();
|
||||
egui_winit.handle_output(surface.window(), &egui_ctx, egui_output);
|
||||
// Automatically start the next render subpass and draw the gui
|
||||
let size = surface.window().inner_size();
|
||||
let sf: f32 = surface.window().scale_factor() as f32;
|
||||
egui_painter
|
||||
.draw(
|
||||
&mut builder,
|
||||
&dynamic_state,
|
||||
[size.width as f32, size.height as f32],
|
||||
[(size.width as f32) / sf, (size.height as f32) / sf],
|
||||
&egui_ctx,
|
||||
clipped_shapes,
|
||||
egui_output.shapes,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -294,9 +382,16 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
|
||||
let command_buffer = builder.build().unwrap();
|
||||
|
||||
if wait_for_last_frame {
|
||||
if let Some(FrameEndFuture::FenceSignalFuture(ref mut f)) = previous_frame_end {
|
||||
f.wait(None).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let future = previous_frame_end
|
||||
.take()
|
||||
.unwrap()
|
||||
.get()
|
||||
.join(acquire_future)
|
||||
.then_execute(queue.clone(), command_buffer)
|
||||
.unwrap()
|
||||
@ -305,15 +400,15 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
|
||||
match future {
|
||||
Ok(future) => {
|
||||
previous_frame_end = Some(future.boxed());
|
||||
previous_frame_end = Some(FrameEndFuture::FenceSignalFuture(future));
|
||||
}
|
||||
Err(FlushError::OutOfDate) => {
|
||||
recreate_swap_chain = true;
|
||||
previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||
recreate_swapchain = true;
|
||||
previous_frame_end = Some(FrameEndFuture::now(device.clone()));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to flush future: {:?}", e);
|
||||
previous_frame_end = Some(sync::now(device.clone()).boxed());
|
||||
previous_frame_end = Some(FrameEndFuture::now(device.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,28 +420,23 @@ pub fn run_app(amd_gui: Arc<Mutex<AmdGui>>, _receiver: UnboundedReceiver<bool>)
|
||||
fn window_size_dependent_setup(
|
||||
images: &[Arc<SwapchainImage<Window>>],
|
||||
render_pass: Arc<RenderPass>,
|
||||
dynamic_state: &mut DynamicState,
|
||||
) -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
|
||||
let dimensions = images[0].dimensions();
|
||||
|
||||
let viewport = vulkano::pipeline::viewport::Viewport {
|
||||
origin: [0.0, 0.0],
|
||||
dimensions: [dimensions[0] as f32, dimensions[1] as f32],
|
||||
depth_range: 0.0..1.0,
|
||||
};
|
||||
dynamic_state.viewports = Some(vec![viewport]);
|
||||
viewport: &mut Viewport,
|
||||
) -> Vec<Arc<Framebuffer>> {
|
||||
let dimensions = images[0].dimensions().width_height();
|
||||
viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32];
|
||||
|
||||
images
|
||||
.iter()
|
||||
.map(|image| {
|
||||
let view = ImageView::new(image.clone()).unwrap();
|
||||
Arc::new(
|
||||
Framebuffer::start(render_pass.clone())
|
||||
.add(view)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap(),
|
||||
) as Arc<dyn FramebufferAbstract + Send + Sync>
|
||||
let view = ImageView::new_default(image.clone()).unwrap();
|
||||
Framebuffer::new(
|
||||
render_pass.clone(),
|
||||
FramebufferCreateInfo {
|
||||
attachments: vec![view],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
@ -2,10 +2,9 @@
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use arrows::*;
|
||||
use egui::Pos2;
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
|
||||
pub use arrows::*;
|
||||
pub use h_line::*;
|
||||
pub use line::*;
|
||||
pub use marker_shape::*;
|
||||
@ -129,7 +128,8 @@ impl ToString for LineStyle {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Describes a function y = f(x) with an optional range for x and a number of points.
|
||||
/// Describes a function y = f(x) with an optional range for x and a number of
|
||||
/// points.
|
||||
pub struct ExplicitGenerator {
|
||||
function: Box<dyn Fn(f64) -> f64>,
|
||||
x_range: RangeInclusive<f64>,
|
||||
@ -138,8 +138,8 @@ pub struct ExplicitGenerator {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Returns the x-coordinate of a possible intersection between a line segment from `p1` to `p2` and
|
||||
/// a horizontal line at the given y-coordinate.
|
||||
/// Returns the x-coordinate of a possible intersection between a line segment
|
||||
/// from `p1` to `p2` and a horizontal line at the given y-coordinate.
|
||||
#[inline(always)]
|
||||
pub fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
|
||||
((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y))
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::values::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A set of arrows.
|
||||
pub struct Arrows {
|
||||
|
@ -1,11 +1,13 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::items::LineStyle;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A horizontal line in a plot, filling the full width
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -28,7 +30,8 @@ impl HLine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
|
||||
/// be auto-assigned.
|
||||
#[must_use]
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.stroke.color = color.into();
|
||||
|
@ -1,12 +1,14 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::{pos2, NumExt, Ui};
|
||||
use epaint::{Color32, Mesh, Rgba, Shape, Stroke};
|
||||
|
||||
use crate::items;
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::{pos2, NumExt, Ui};
|
||||
use epaint::{Color32, Mesh, Rgba, Shape, Stroke};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
impl PlotItem for Line {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
@ -120,7 +122,8 @@ impl Line {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
|
||||
/// be auto-assigned.
|
||||
#[must_use]
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.stroke.color = color.into();
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::{pos2, Image, Rect, Ui, Vec2};
|
||||
use epaint::{Color32, Shape, Stroke, TextureId};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::{pos2, Image, Rect, Ui, Vec2};
|
||||
use epaint::{Color32, Shape, Stroke, TextureId};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// An image in the plot.
|
||||
pub struct PlotImage {
|
||||
@ -65,8 +67,8 @@ impl PlotImage {
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
///
|
||||
/// Multiple plot items may share the same name, in which case they will also share an entry in
|
||||
/// the legend.
|
||||
/// Multiple plot items may share the same name, in which case they will
|
||||
/// also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::items::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::items::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
|
||||
/// Trait shared by things that can be drawn in the plot.
|
||||
pub trait PlotItem {
|
||||
|
@ -1,17 +1,20 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::{pos2, vec2, Pos2, Ui};
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
|
||||
use crate::items::marker_shape::MarkerShape;
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::{pos2, vec2, Pos2, Ui};
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A set of points.
|
||||
pub struct Points {
|
||||
pub(crate) series: Values,
|
||||
pub(crate) shape: MarkerShape,
|
||||
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
|
||||
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked
|
||||
/// automatically.
|
||||
pub(crate) color: Color32,
|
||||
/// Whether to fill the marker. Does not apply to all types.
|
||||
pub(crate) filled: bool,
|
||||
@ -64,7 +67,8 @@ impl Points {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to add stems between the markers and a horizontal reference line.
|
||||
/// Whether to add stems between the markers and a horizontal reference
|
||||
/// line.
|
||||
#[must_use]
|
||||
pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
|
||||
self.stems = Some(y_reference.into());
|
||||
@ -82,8 +86,8 @@ impl Points {
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
///
|
||||
/// Multiple plot items may share the same name, in which case they will also share an entry in
|
||||
/// the legend.
|
||||
/// Multiple plot items may share the same name, in which case they will
|
||||
/// also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::{NumExt, Ui};
|
||||
use epaint::{Color32, Rgba, Shape, Stroke};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::values::Values;
|
||||
use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::{NumExt, Ui};
|
||||
use epaint::{Color32, Rgba, Shape, Stroke};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// A convex polygon.
|
||||
pub struct Polygon {
|
||||
@ -28,8 +30,8 @@ impl Polygon {
|
||||
}
|
||||
}
|
||||
|
||||
/// Highlight this polygon in the plot by scaling up the stroke and reducing the fill
|
||||
/// transparency.
|
||||
/// Highlight this polygon in the plot by scaling up the stroke and reducing
|
||||
/// the fill transparency.
|
||||
#[must_use]
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
@ -50,7 +52,8 @@ impl Polygon {
|
||||
self
|
||||
}
|
||||
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
|
||||
/// be auto-assigned.
|
||||
#[must_use]
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.stroke.color = color.into();
|
||||
@ -75,8 +78,8 @@ impl Polygon {
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
///
|
||||
/// Multiple plot items may share the same name, in which case they will also share an entry in
|
||||
/// the legend.
|
||||
/// Multiple plot items may share the same name, in which case they will
|
||||
/// also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::{Align2, Rect, TextStyle, Ui};
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::{Align2, Rect, Ui};
|
||||
use epaint::{Color32, Shape, Stroke, TextStyle};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Text inside the plot.
|
||||
pub struct Text {
|
||||
@ -45,7 +47,8 @@ impl Text {
|
||||
self
|
||||
}
|
||||
|
||||
/// Text color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Text color. Default is `Color32::TRANSPARENT` which means a color will
|
||||
/// be auto-assigned.
|
||||
#[must_use]
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.color = color.into();
|
||||
@ -63,8 +66,8 @@ impl Text {
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
///
|
||||
/// Multiple plot items may share the same name, in which case they will also share an entry in
|
||||
/// the legend.
|
||||
/// Multiple plot items may share the same name, in which case they will
|
||||
/// also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
@ -80,10 +83,11 @@ impl PlotItem for Text {
|
||||
} else {
|
||||
self.color
|
||||
};
|
||||
let fond_id = ui.style().text_styles.get(&self.style).unwrap();
|
||||
let pos = transform.position_from_value(&self.position);
|
||||
let galley = ui
|
||||
.fonts()
|
||||
.layout_no_wrap(self.text.clone(), self.style, color);
|
||||
.layout_no_wrap(self.text.clone(), fond_id.clone(), color);
|
||||
let rect = self
|
||||
.anchor
|
||||
.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
|
@ -1,11 +1,13 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
|
||||
use crate::items::plot_item::PlotItem;
|
||||
use crate::items::value::Value;
|
||||
use crate::items::values::Values;
|
||||
use crate::items::LineStyle;
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use egui::Ui;
|
||||
use epaint::{Color32, Shape, Stroke};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
impl PlotItem for VLine {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
@ -95,7 +97,8 @@ impl VLine {
|
||||
self
|
||||
}
|
||||
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
|
||||
/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
|
||||
/// be auto-assigned.
|
||||
#[must_use]
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.stroke.color = color.into();
|
||||
@ -113,8 +116,8 @@ impl VLine {
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
///
|
||||
/// Multiple plot items may share the same name, in which case they will also share an entry in
|
||||
/// the legend.
|
||||
/// Multiple plot items may share the same name, in which case they will
|
||||
/// also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[must_use]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
|
@ -4,8 +4,8 @@
|
||||
/// large values (e.g. unix time on x axis).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Value {
|
||||
/// This is often something monotonically increasing, such as time, but doesn't have to be.
|
||||
/// Goes from left to right.
|
||||
/// This is often something monotonically increasing, such as time, but
|
||||
/// doesn't have to be. Goes from left to right.
|
||||
pub x: f64,
|
||||
/// Goes from bottom to top (inverse of everything else in egui!).
|
||||
pub y: f64,
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::items::{ExplicitGenerator, Value};
|
||||
use crate::transform::Bounds;
|
||||
use std::collections::Bound;
|
||||
use std::ops::{RangeBounds, RangeInclusive};
|
||||
|
||||
use crate::items::{ExplicitGenerator, Value};
|
||||
use crate::transform::Bounds;
|
||||
|
||||
pub struct Values {
|
||||
pub values: Vec<Value>,
|
||||
generator: Option<ExplicitGenerator>,
|
||||
@ -20,7 +21,8 @@ impl Values {
|
||||
Self::from_values(iter.collect())
|
||||
}
|
||||
|
||||
/// Draw a line based on a function `y=f(x)`, a range (which can be infinite) for x and the number of points.
|
||||
/// Draw a line based on a function `y=f(x)`, a range (which can be
|
||||
/// infinite) for x and the number of points.
|
||||
pub fn from_explicit_callback(
|
||||
function: impl Fn(f64) -> f64 + 'static,
|
||||
x_range: impl RangeBounds<f64>,
|
||||
@ -48,8 +50,9 @@ impl Values {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a line based on a function `(x,y)=f(t)`, a range for t and the number of points.
|
||||
/// The range may be specified as start..end or as start..=end.
|
||||
/// Draw a line based on a function `(x,y)=f(t)`, a range for t and the
|
||||
/// number of points. The range may be specified as start..end or as
|
||||
/// start..=end.
|
||||
pub fn from_parametric_callback(
|
||||
function: impl Fn(f64) -> (f64, f64),
|
||||
t_range: impl RangeBounds<f64>,
|
||||
@ -92,13 +95,14 @@ impl Values {
|
||||
Self::from_values(values)
|
||||
}
|
||||
|
||||
/// Returns true if there are no data points available and there is no function to generate any.
|
||||
/// Returns true if there are no data points available and there is no
|
||||
/// function to generate any.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.generator.is_none() && self.values.is_empty()
|
||||
}
|
||||
|
||||
/// If initialized with a generator function, this will generate `n` evenly spaced points in the
|
||||
/// given range.
|
||||
/// If initialized with a generator function, this will generate `n` evenly
|
||||
/// spaced points in the given range.
|
||||
pub fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
|
||||
if let Some(generator) = self.generator.take() {
|
||||
if let Some(intersection) = Self::range_intersection(&x_range, &generator.x_range) {
|
||||
|
@ -117,7 +117,8 @@ impl Bounds {
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the screen rectangle and the plot bounds and provides methods to transform them.
|
||||
/// Contains the screen rectangle and the plot bounds and provides methods to
|
||||
/// transform them.
|
||||
#[derive(Clone)]
|
||||
pub struct ScreenTransform {
|
||||
/// The screen rectangle.
|
||||
|
@ -1,12 +1,13 @@
|
||||
use amdgpu::helper_cmd::Command;
|
||||
use amdgpu::pidfile::helper_cmd::{send_command, Command, Response};
|
||||
use amdgpu_config::fan::MatrixPoint;
|
||||
use egui::{emath, pos2, Layout, PointerButton, Ui};
|
||||
use epaint::Color32;
|
||||
|
||||
use crate::app::{ChangeState, FanConfig, FanServices, StatefulConfig};
|
||||
use crate::widgets;
|
||||
use crate::widgets::drag_plot::PlotMsg;
|
||||
use crate::widgets::reload_section::ReloadSection;
|
||||
use crate::{widgets, widgets::ConfigFile};
|
||||
use crate::widgets::ConfigFile;
|
||||
|
||||
pub struct ChangeFanSettings {
|
||||
config: FanConfig,
|
||||
@ -139,10 +140,10 @@ impl ChangeFanSettings {
|
||||
ui.label("Saving...");
|
||||
}
|
||||
ChangeState::Success => {
|
||||
ui.add(egui::Label::new("Saved").text_color(Color32::GREEN));
|
||||
ui.add(egui::Label::new("Saved")/*.text_color(Color32::GREEN)*/);
|
||||
}
|
||||
ChangeState::Failure(msg) => {
|
||||
ui.add(egui::Label::new(format!("Failure. {}", msg)).text_color(Color32::RED));
|
||||
ui.add(egui::Label::new(format!("Failure. {}", msg))/*.text_color(Color32::RED)*/);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -164,11 +165,11 @@ impl ChangeFanSettings {
|
||||
path: String::from(config.path()),
|
||||
content,
|
||||
};
|
||||
match amdgpu::helper_cmd::send_command(command) {
|
||||
Ok(amdgpu::helper_cmd::Response::ConfigFileSaveFailed(msg)) => {
|
||||
match send_command(command) {
|
||||
Ok(Response::ConfigFileSaveFailed(msg)) => {
|
||||
state.state = ChangeState::Failure(msg);
|
||||
}
|
||||
Ok(amdgpu::helper_cmd::Response::ConfigFileSaved) => {
|
||||
Ok(Response::ConfigFileSaved) => {
|
||||
state.state = ChangeState::Success;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use egui::{PointerButton, Response, Sense, Ui, Widget};
|
||||
|
||||
use amdgpu_config::fan::MatrixPoint;
|
||||
use egui::{PointerButton, Response, Sense, Ui, Widget};
|
||||
|
||||
use crate::app::FanConfig;
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
use crate::app::{FanConfig, FanServices};
|
||||
use amdgpu::Card;
|
||||
use amdmond_lib::AmdMon;
|
||||
use core::option::Option;
|
||||
use core::option::Option::Some;
|
||||
use egui::Ui;
|
||||
use std::collections::vec_deque::VecDeque;
|
||||
|
||||
use amdgpu::Card;
|
||||
use amdmond_lib::AmdMon;
|
||||
use egui::Ui;
|
||||
|
||||
use crate::app::{FanConfig, FanServices};
|
||||
|
||||
pub struct CoolingPerformance {
|
||||
capacity: usize,
|
||||
data: VecDeque<f64>,
|
||||
@ -55,18 +57,20 @@ impl CoolingPerformance {
|
||||
.name("Overheating")
|
||||
.color(Color32::DARK_RED);
|
||||
|
||||
let plot = Plot::new("cooling performance")
|
||||
ui.label("Temperature");
|
||||
Plot::new("cooling performance")
|
||||
.allow_drag(false)
|
||||
.allow_zoom(false)
|
||||
.height(600.0)
|
||||
.line(curve)
|
||||
.hline(zero)
|
||||
.hline(optimal)
|
||||
.hline(target)
|
||||
.legend(Legend::default());
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.line(curve);
|
||||
plot_ui.hline(zero);
|
||||
plot_ui.hline(optimal);
|
||||
plot_ui.hline(target);
|
||||
// plot_ui.legend(Legend::default());
|
||||
});
|
||||
|
||||
ui.label("Temperature");
|
||||
ui.add(plot);
|
||||
// ui.add(plot);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Current temperature");
|
||||
ui.label(format!("{:<3.2}°C", current));
|
||||
|
@ -1,10 +1,9 @@
|
||||
use egui::{emath, vec2, CursorIcon, Id, NumExt, PointerButton, Response, Sense, Ui, Vec2};
|
||||
use epaint::ahash::AHashSet;
|
||||
use epaint::color::Hsva;
|
||||
use epaint::Color32;
|
||||
use epaint::{Color32, Rounding};
|
||||
|
||||
use crate::items::HLine;
|
||||
use crate::items::*;
|
||||
use crate::items::{HLine, *};
|
||||
use crate::transform::{Bounds, ScreenTransform};
|
||||
use crate::widgets::drag_plot_prepared::DragPlotPrepared;
|
||||
use crate::widgets::legend::Legend;
|
||||
@ -16,7 +15,8 @@ pub enum PlotMsg {
|
||||
Drag(emath::Vec2),
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
// , serde::Serialize, serde::Deserialize
|
||||
#[derive(Clone)]
|
||||
struct PlotMemory {
|
||||
bounds: Bounds,
|
||||
auto_bounds: bool,
|
||||
@ -125,12 +125,13 @@ where
|
||||
self.next_auto_color_idx += 1;
|
||||
let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875
|
||||
let h = i as f32 * golden_ratio;
|
||||
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other perspective color space
|
||||
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other
|
||||
// perspective color space
|
||||
}
|
||||
|
||||
/// width / height ratio of the data.
|
||||
/// For instance, it can be useful to set this to `1.0` for when the two axes show the same
|
||||
/// unit.
|
||||
/// For instance, it can be useful to set this to `1.0` for when the two
|
||||
/// axes show the same unit.
|
||||
/// By default the plot window's aspect ratio is used.
|
||||
#[must_use]
|
||||
pub fn data_aspect(mut self, data_aspect: f32) -> Self {
|
||||
@ -139,7 +140,8 @@ where
|
||||
}
|
||||
|
||||
/// width / height ratio of the plot region.
|
||||
/// By default no fixed aspect ratio is set (and width/height will fill the ui it is in).
|
||||
/// By default no fixed aspect ratio is set (and width/height will fill the
|
||||
/// ui it is in).
|
||||
#[must_use]
|
||||
pub fn view_aspect(mut self, view_aspect: f32) -> Self {
|
||||
self.view_aspect = Some(view_aspect);
|
||||
@ -147,7 +149,8 @@ where
|
||||
}
|
||||
|
||||
/// Width of plot. By default a plot will fill the ui it is in.
|
||||
/// If you set [`Self::view_aspect`], the width can be calculated from the height.
|
||||
/// If you set [`Self::view_aspect`], the width can be calculated from the
|
||||
/// height.
|
||||
#[must_use]
|
||||
pub fn width(mut self, width: f32) -> Self {
|
||||
self.min_size.x = width;
|
||||
@ -156,7 +159,8 @@ where
|
||||
}
|
||||
|
||||
/// Height of plot. By default a plot will fill the ui it is in.
|
||||
/// If you set [`Self::view_aspect`], the height can be calculated from the width.
|
||||
/// If you set [`Self::view_aspect`], the height can be calculated from the
|
||||
/// width.
|
||||
#[must_use]
|
||||
pub fn height(mut self, height: f32) -> Self {
|
||||
self.min_size.y = height;
|
||||
@ -240,9 +244,9 @@ where
|
||||
} = self;
|
||||
let plot_id = ui.make_persistent_id(id);
|
||||
let memory = ui
|
||||
.memory()
|
||||
.id_data
|
||||
.get_mut_or_insert_with(plot_id, || PlotMemory {
|
||||
.ctx()
|
||||
.data()
|
||||
.get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
|
||||
bounds: min_auto_bounds,
|
||||
auto_bounds: false,
|
||||
hovered_entry: None,
|
||||
@ -284,11 +288,11 @@ where
|
||||
};
|
||||
|
||||
let (rect, response) = ui.allocate_exact_size(size, Sense::click_and_drag());
|
||||
let plot_painter = ui.painter().sub_region(rect);
|
||||
let plot_painter = ui.painter().with_clip_rect(rect);
|
||||
|
||||
plot_painter.add(epaint::RectShape {
|
||||
rect,
|
||||
corner_radius: 2.0,
|
||||
rounding: Rounding::from(2.0),
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
});
|
||||
@ -408,16 +412,15 @@ where
|
||||
hovered_entry = legend.get_hovered_entry_name();
|
||||
}
|
||||
|
||||
ui.memory().id_data.insert(
|
||||
plot_id,
|
||||
PlotMemory {
|
||||
ui.ctx()
|
||||
.data()
|
||||
.get_persisted_mut_or_insert_with(plot_id, || PlotMemory {
|
||||
bounds: t_bounds,
|
||||
auto_bounds,
|
||||
hovered_entry,
|
||||
hidden_items,
|
||||
min_auto_bounds,
|
||||
},
|
||||
);
|
||||
});
|
||||
response.on_hover_cursor(CursorIcon::Crosshair)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
use egui::{emath, pos2, remap_clamp, vec2, Align2, Layout, NumExt, Pos2, Response, Ui};
|
||||
use epaint::{Color32, Rgba, Shape, Stroke, TextStyle};
|
||||
use egui::{emath, pos2, remap_clamp, vec2, Align2, Layout, NumExt, Pos2, Response, TextStyle, Ui};
|
||||
use epaint::{Color32, Rgba, Shape, Stroke};
|
||||
|
||||
use crate::items::Value;
|
||||
use crate::items::{Line, PlotItem};
|
||||
use crate::items::{Line, PlotItem, Value};
|
||||
use crate::transform::ScreenTransform;
|
||||
|
||||
pub struct DragPlotPrepared {
|
||||
@ -41,7 +40,9 @@ impl DragPlotPrepared {
|
||||
self.hover(ui, pointer, &mut shapes);
|
||||
}
|
||||
|
||||
ui.painter().sub_region(*transform.frame()).extend(shapes);
|
||||
ui.painter()
|
||||
.with_clip_rect(*transform.frame())
|
||||
.extend(shapes);
|
||||
}
|
||||
|
||||
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
||||
@ -77,9 +78,12 @@ impl DragPlotPrepared {
|
||||
|
||||
let n = (value_main / step_size).round() as i64;
|
||||
let spacing_in_points = if n % (base * base) == 0 {
|
||||
step_size_in_points * (base_f * base_f) as f32 // think line (multiple of 100)
|
||||
step_size_in_points * (base_f * base_f) as f32 // think line
|
||||
// (multiple of
|
||||
// 100)
|
||||
} else if n % base == 0 {
|
||||
step_size_in_points * base_f as f32 // medium line (multiple of 10)
|
||||
step_size_in_points * base_f as f32 // medium line (multiple of
|
||||
// 10)
|
||||
} else {
|
||||
step_size_in_points // thin line
|
||||
};
|
||||
@ -106,7 +110,11 @@ impl DragPlotPrepared {
|
||||
let color = color_from_alpha(ui, text_alpha);
|
||||
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack
|
||||
|
||||
let galley = ui.painter().layout_no_wrap(text, text_style, color);
|
||||
let galley = ui.painter().layout_no_wrap(
|
||||
text,
|
||||
ui.style().text_styles.get(&text_style).cloned().unwrap(),
|
||||
color,
|
||||
);
|
||||
|
||||
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
|
||||
|
||||
@ -135,15 +143,13 @@ impl DragPlotPrepared {
|
||||
let interact_radius: f32 = 16.0;
|
||||
let mut closest_value = None;
|
||||
let mut closest_dist_sq = interact_radius.powi(2);
|
||||
for item in lines {
|
||||
if let Some(values) = item.values() {
|
||||
for (idx, value) in values.values.iter().enumerate() {
|
||||
let pos = transform.position_from_value(value);
|
||||
let dist_sq = pointer.distance_sq(pos);
|
||||
if dist_sq < closest_dist_sq {
|
||||
closest_dist_sq = dist_sq;
|
||||
closest_value = Some(idx);
|
||||
}
|
||||
for values in lines.iter().filter_map(|v| v.values()) {
|
||||
for (idx, value) in values.values.iter().enumerate() {
|
||||
let pos = transform.position_from_value(value);
|
||||
let dist_sq = pointer.distance_sq(pos);
|
||||
if dist_sq < closest_dist_sq {
|
||||
closest_dist_sq = dist_sq;
|
||||
closest_value = Some(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,11 +261,15 @@ impl DragPlotPrepared {
|
||||
};
|
||||
|
||||
shapes.push(Shape::text(
|
||||
ui.fonts(),
|
||||
&*ui.fonts(),
|
||||
pointer + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Body,
|
||||
ui.style()
|
||||
.text_styles
|
||||
.get(&TextStyle::Body)
|
||||
.cloned()
|
||||
.unwrap(),
|
||||
ui.visuals().text_color(),
|
||||
));
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
use std::string::String;
|
||||
|
||||
use egui::{pos2, vec2, Align, PointerButton, Rect, Response, Sense, WidgetInfo, WidgetType};
|
||||
use epaint::{Color32, TextStyle};
|
||||
use egui::{
|
||||
pos2, vec2, Align, PointerButton, Rect, Response, Sense, TextStyle, WidgetInfo, WidgetType,
|
||||
};
|
||||
use epaint::Color32;
|
||||
|
||||
/// Where to place the plot legend.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -26,7 +28,7 @@ impl Corner {
|
||||
}
|
||||
|
||||
/// The configuration for a plot legend.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Legend {
|
||||
pub text_style: TextStyle,
|
||||
pub background_alpha: f32,
|
||||
@ -89,9 +91,15 @@ impl LegendEntry {
|
||||
hovered,
|
||||
} = self;
|
||||
|
||||
let galley =
|
||||
ui.fonts()
|
||||
.layout_delayed_color(text, ui.style().body_text_style, f32::INFINITY);
|
||||
let galley = ui.fonts().layout_delayed_color(
|
||||
text,
|
||||
ui.style()
|
||||
.text_styles
|
||||
.get(&TextStyle::Body)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
f32::INFINITY,
|
||||
);
|
||||
|
||||
let icon_size = galley.size().y;
|
||||
let icon_spacing = icon_size / 5.0;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use egui::{vec2, Align, Direction, Frame, Layout, Rect, Response, Ui, Widget};
|
||||
use egui::style::Margin;
|
||||
use egui::{Align, Direction, Frame, Layout, Rect, Response, Ui, Widget};
|
||||
use epaint::ahash::AHashSet;
|
||||
use epaint::Color32;
|
||||
|
||||
@ -15,8 +16,8 @@ pub struct LegendWidget {
|
||||
}
|
||||
|
||||
impl LegendWidget {
|
||||
/// Create a new legend from items, the names of items that are hidden and the style of the
|
||||
/// text. Returns `None` if the legend has no entries.
|
||||
/// Create a new legend from items, the names of items that are hidden and
|
||||
/// the style of the text. Returns `None` if the legend has no entries.
|
||||
pub fn try_new(
|
||||
rect: Rect,
|
||||
config: Legend,
|
||||
@ -89,10 +90,11 @@ impl Widget for &mut LegendWidget {
|
||||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
||||
legend_ui
|
||||
.scope(|ui| {
|
||||
ui.style_mut().body_text_style = config.text_style;
|
||||
// ui.style_mut().body_text_style = config.text_style;
|
||||
let background_frame = Frame {
|
||||
margin: vec2(8.0, 4.0),
|
||||
corner_radius: ui.style().visuals.window_corner_radius,
|
||||
inner_margin: Margin::symmetric(8.0, 4.0),
|
||||
outer_margin: Default::default(),
|
||||
rounding: ui.style().visuals.window_rounding,
|
||||
shadow: epaint::Shadow::default(),
|
||||
fill: ui.style().visuals.extreme_bg_color,
|
||||
stroke: ui.style().visuals.window_stroke(),
|
||||
|
@ -5,6 +5,8 @@ pub mod drag_plot;
|
||||
pub mod drag_plot_prepared;
|
||||
pub mod legend;
|
||||
pub mod legend_widget;
|
||||
pub mod output_widget;
|
||||
pub mod outputs_settings;
|
||||
pub mod reload_section;
|
||||
|
||||
pub use change_fan_settings::*;
|
||||
|
69
amdguid/src/widgets/output_widget.rs
Normal file
69
amdguid/src/widgets/output_widget.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use amdgpu::pidfile::ports::Output;
|
||||
use egui::{Response, Sense, Ui, Vec2};
|
||||
use epaint::Stroke;
|
||||
|
||||
pub struct OutputWidget<'output, 'textures> {
|
||||
output: &'output Output,
|
||||
textures: &'textures HashMap<String, egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl<'output, 'textures> OutputWidget<'output, 'textures> {
|
||||
pub fn new(
|
||||
output: &'output Output,
|
||||
textures: &'textures HashMap<String, egui::TextureHandle>,
|
||||
) -> Self {
|
||||
Self { output, textures }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'output, 'textures> egui::Widget for OutputWidget<'output, 'textures> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let (rect, res) = ui.allocate_exact_size(Vec2::new(80.0, 70.0), Sense::click());
|
||||
// let _transform = ScreenTransform::new(rect, Bounds::new_symmetrical(1.0),
|
||||
// false, false); let _frame = *transform.frame();
|
||||
// eprintln!("min {:?} max {:?}", frame.min, frame.max);
|
||||
let painter = ui.painter(); //.sub_region(*transform.frame());
|
||||
|
||||
painter.rect_filled(rect, 0.0, epaint::color::Color32::DARK_RED);
|
||||
painter.rect(rect, 2.0, epaint::color::Color32::DARK_RED, {
|
||||
let mut s = Stroke::default();
|
||||
s.color = epaint::color::Color32::GREEN;
|
||||
s.width = 1.0;
|
||||
s
|
||||
});
|
||||
|
||||
let rect_middle_point = (rect.max - rect.min) / 2.0;
|
||||
|
||||
painter.circle_filled(
|
||||
rect.min + Vec2::new(rect_middle_point.x / 2.0, rect_middle_point.y),
|
||||
3.0,
|
||||
epaint::color::Color32::GREEN,
|
||||
);
|
||||
painter.circle_filled(
|
||||
rect.min + rect_middle_point,
|
||||
3.0,
|
||||
epaint::color::Color32::GREEN,
|
||||
);
|
||||
|
||||
painter.circle_filled(
|
||||
rect.min
|
||||
+ Vec2::new(
|
||||
rect_middle_point.x + (rect_middle_point.x / 2.0),
|
||||
rect_middle_point.y,
|
||||
),
|
||||
3.0,
|
||||
epaint::color::Color32::GREEN,
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
fn icon(name: &str, textures: &HashMap<String, egui::TextureHandle>) {
|
||||
if let Some(_texture) = textures.get(name) {
|
||||
//
|
||||
} else {
|
||||
//
|
||||
}
|
||||
}
|
86
amdguid/src/widgets/outputs_settings.rs
Normal file
86
amdguid/src/widgets/outputs_settings.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use amdgpu::pidfile::ports::{Output, Status};
|
||||
use egui::Ui;
|
||||
use epaint::ColorImage;
|
||||
use image::ImageFormat;
|
||||
|
||||
use crate::app::StatefulConfig;
|
||||
use crate::widgets::output_widget::OutputWidget;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OutputsSettings {
|
||||
textures: HashMap<String, egui::TextureHandle>,
|
||||
}
|
||||
|
||||
impl OutputsSettings {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
_state: &mut StatefulConfig,
|
||||
outputs: &BTreeMap<String, Vec<Output>>,
|
||||
) {
|
||||
outputs.values().flatten().for_each(|output| {
|
||||
// 160x160
|
||||
let image = {
|
||||
let bytes = include_bytes!("../../assets/icons/ports.jpg");
|
||||
image::load_from_memory(bytes).unwrap()
|
||||
};
|
||||
|
||||
for (_idx, _pixel, _p) in image.to_rgba8().enumerate_pixels() {
|
||||
// let bytes = pixel.;
|
||||
// eprintln!("{:?}", bytes);
|
||||
}
|
||||
|
||||
if !self.textures.contains_key(&output.port_type) {
|
||||
let img = image::load_from_memory_with_format(
|
||||
include_bytes!("../../assets/icons/ports.jpg"),
|
||||
ImageFormat::Jpeg,
|
||||
)
|
||||
.unwrap();
|
||||
let image_buffer = img.to_rgba8();
|
||||
let size = [image.width() as _, image.height() as _];
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
let _ = ui.ctx().load_texture(
|
||||
output.port_type.clone(),
|
||||
ColorImage::from_rgba_unmultiplied(size, pixels.as_slice()),
|
||||
);
|
||||
}
|
||||
});
|
||||
let _available = ui.available_rect_before_wrap();
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal_top(|ui| {
|
||||
outputs.iter().for_each(|(name, outputs)| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(format!("name {name}"));
|
||||
ui.horizontal_top(|ui| {
|
||||
outputs.iter().for_each(|output| {
|
||||
ui.vertical(|ui| {
|
||||
ui.add(OutputWidget::new(output, &self.textures));
|
||||
|
||||
ui.label(format!("port_number {}", output.port_number));
|
||||
ui.label(format!("port_type {}", output.port_type));
|
||||
ui.label(format!("card {}", output.card));
|
||||
ui.label(format!(
|
||||
"port_name {}",
|
||||
output.port_name.as_deref().unwrap_or_default()
|
||||
));
|
||||
ui.label(match output.status {
|
||||
Status::Connected => "Connected",
|
||||
Status::Disconnected => "Disconnected",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// eprintln!("==============================================================");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn vga_port_icon() {
|
||||
// Texture
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use crate::app::{ChangeState, FanServices};
|
||||
use amdgpu::helper_cmd::Command;
|
||||
use amdgpu::pidfile::helper_cmd::Command;
|
||||
use egui::{PointerButton, Response, Sense, Ui};
|
||||
use epaint::Color32;
|
||||
|
||||
use crate::app::{ChangeState, FanServices};
|
||||
|
||||
pub struct ReloadSection<'l> {
|
||||
pub services: &'l mut FanServices,
|
||||
@ -18,7 +18,7 @@ impl<'l> egui::Widget for ReloadSection<'l> {
|
||||
if ui.button("Reload").clicked_by(PointerButton::Primary) {
|
||||
service.reload = ChangeState::Reloading;
|
||||
|
||||
match amdgpu::helper_cmd::send_command(Command::ReloadConfig {
|
||||
match amdgpu::pidfile::helper_cmd::send_command(Command::ReloadConfig {
|
||||
pid: service.pid,
|
||||
}) {
|
||||
Ok(response) => {
|
||||
@ -37,12 +37,13 @@ impl<'l> egui::Widget for ReloadSection<'l> {
|
||||
ui.label("Reloading...");
|
||||
}
|
||||
ChangeState::Success => {
|
||||
ui.add(egui::Label::new("Reloaded").text_color(Color32::DARK_GREEN));
|
||||
ui.add(
|
||||
egui::Label::new("Reloaded"), /* .text_color(Color32::DARK_GREEN) */
|
||||
);
|
||||
}
|
||||
ChangeState::Failure(msg) => {
|
||||
ui.add(
|
||||
egui::Label::new(format!("Failure. {}", msg))
|
||||
.text_color(Color32::RED),
|
||||
egui::Label::new(format!("Failure. {}", msg)), // .text_color(Color32::RED),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
pub mod errors;
|
||||
|
||||
use crate::errors::AmdMonError;
|
||||
use amdgpu::hw_mon::HwMon;
|
||||
use amdgpu::utils::load_temp_inputs;
|
||||
use amdgpu::{
|
||||
@ -8,6 +7,8 @@ use amdgpu::{
|
||||
};
|
||||
use amdgpu_config::fan;
|
||||
|
||||
use crate::errors::AmdMonError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AmdMonError>;
|
||||
|
||||
pub struct AmdMon {
|
||||
|
@ -2,11 +2,11 @@ mod command;
|
||||
mod log_file;
|
||||
mod watch;
|
||||
|
||||
use amdgpu::utils::ensure_config_dir;
|
||||
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::Command;
|
||||
use amdgpu::utils::ensure_config_dir;
|
||||
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
|
||||
|
||||
#[derive(gumdrop::Options)]
|
||||
pub struct Opts {
|
||||
|
@ -1,13 +1,12 @@
|
||||
use amdgpu::ports::{sock_file, Command, Response};
|
||||
use amdgpu::{ports::*, IoFailure};
|
||||
use std::fs::{DirEntry, Permissions};
|
||||
use std::io::{Read, Write};
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use amdgpu::pidfile::ports::{sock_file, Command, Response, *};
|
||||
use amdgpu::IoFailure;
|
||||
|
||||
fn parse_output(entry: DirEntry) -> Option<Output> {
|
||||
let ty = entry.file_type().ok()?;
|
||||
if ty.is_dir() {
|
||||
@ -60,45 +59,7 @@ async fn read_outputs(state: Arc<Mutex<Vec<Output>>>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Service(UnixStream);
|
||||
|
||||
impl Service {
|
||||
/// Serialize and send command
|
||||
pub fn write_response(&mut self, res: Response) {
|
||||
match ron::to_string(&res) {
|
||||
Ok(buffer) => match self.0.write_all(buffer.as_bytes()) {
|
||||
Ok(_) => {
|
||||
log::info!("Response successfully written")
|
||||
}
|
||||
Err(e) => log::warn!("Failed to write response. {:?}", e),
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to serialize response {:?}. {:?}", res, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from `.sock` file new line separated commands
|
||||
pub fn read_command(&mut self) -> Option<String> {
|
||||
let mut command = String::with_capacity(100);
|
||||
log::info!("Reading stream...");
|
||||
read_line(&mut self.0, &mut command);
|
||||
if command.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(command)
|
||||
}
|
||||
|
||||
/// Close connection with no operation response
|
||||
pub fn kill(mut self) {
|
||||
self.write_response(Response::NoOp);
|
||||
self.close();
|
||||
}
|
||||
|
||||
pub fn close(self) {
|
||||
let _ = self.0.shutdown(Shutdown::Both);
|
||||
}
|
||||
}
|
||||
pub type Service = amdgpu::pidfile::Service<Response>;
|
||||
|
||||
async fn service(state: Arc<Mutex<Vec<Output>>>) {
|
||||
let sock_path = sock_file();
|
||||
@ -121,27 +82,8 @@ async fn service(state: Arc<Mutex<Vec<Output>>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_line(stream: &mut UnixStream, command: &mut String) {
|
||||
let mut buffer = [0];
|
||||
while stream.read_exact(&mut buffer).is_ok() {
|
||||
if buffer[0] == b'\n' {
|
||||
break;
|
||||
}
|
||||
match std::str::from_utf8(&buffer) {
|
||||
Ok(s) => {
|
||||
command.push_str(s);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read from client. {:?}", e);
|
||||
let _ = stream.shutdown(Shutdown::Both);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(stream: UnixStream, state: Arc<Mutex<Vec<Output>>>) {
|
||||
let mut service = Service(stream);
|
||||
let mut service = Service::new(stream);
|
||||
|
||||
let command = match service.read_command() {
|
||||
Some(s) => s,
|
||||
|
@ -1,7 +1,6 @@
|
||||
use gumdrop::Options;
|
||||
|
||||
use amdgpu::utils::ensure_config_dir;
|
||||
use amdgpu_config::voltage::{load_config, Config};
|
||||
use gumdrop::Options;
|
||||
|
||||
use crate::command::VoltageCommand;
|
||||
use crate::error::VoltageError;
|
||||
|
7
rustfmt.toml
Normal file
7
rustfmt.toml
Normal file
@ -0,0 +1,7 @@
|
||||
imports_granularity = "Module"
|
||||
group_imports = "StdExternalCrate"
|
||||
reorder_modules = true
|
||||
reorder_imports = true
|
||||
use_field_init_shorthand = true
|
||||
wrap_comments = true
|
||||
edition = "2021"
|
Loading…
Reference in New Issue
Block a user