feat(gpu): route legacy hardware to OpenGL, add hardware info to About, and expose Graphics options tab

This commit is contained in:
Karim Jerbi committed 2026-10-06 12:23:59 +01:00
1 parent 81bb8d48cf
commit abf302eba5
12 files changed
+838 -21

No files matched your search

+100
View File
@@ -44,6 +44,39 @@ fn info_card<'a>(
.into()
}
/// A full-width label/value line. CPU and GPU names are too long for the
/// fixed-width cards above.
fn info_row<'a>(
label: std::borrow::Cow<'static, str>,
value: impl iced::widget::text::IntoFragment<'a>,
width: Length,
) -> Element<'a, Message> {
container(
row![
text(label).size(11).style(muted_style).width(Length::Fixed(110.0)),
text(value).size(13).width(Fill),
]
.spacing(8)
.align_y(iced::Center),
)
.padding([8, 12])
.width(width)
.style(surface_style)
.into()
}
/// `Adapter (backend)` for the GPU row.
fn gpu_summary() -> String {
#[cfg(target_arch = "wasm32")]
{
"WebGPU / WebGL".to_string()
}
#[cfg(not(target_arch = "wasm32"))]
{
crate::gpu_backend::active_gpu_summary()
}
}
pub(crate) fn platform_name() -> &'static str {
#[cfg(target_arch = "wasm32")]
{
@@ -69,6 +102,59 @@ pub(crate) fn architecture_name() -> &'static str {
}
}
/// The text `Copy Info` puts on the clipboard: everything a bug report needs
/// about the build and the machine, including which graphics backend the
/// resolver picked and why.
pub(crate) fn report() -> String {
use crate::sysinfo::{format_bytes, system_info};
let sys = system_info();
#[allow(unused_mut)]
let mut out = format!(
"Open CAD Studio v{}\nRevision: {}\nCommit date: {}\nProfile: {}\nFeatures: {}\n\n\
[System]\nOS: {} ({} {})\nCPU: {}\nLogical processors: {}\nRAM: {} total, {} available\n",
env!("OCS_FULL_VERSION"),
env!("OCS_GIT_REV"),
env!("OCS_COMMIT_DATE"),
env!("OCS_BUILD_PROFILE"),
env!("OCS_BUILD_FEATURES"),
sys.os,
platform_name(),
architecture_name(),
sys.cpu,
sys.logical_cores,
format_bytes(sys.ram_total),
format_bytes(sys.ram_available),
);
#[cfg(not(target_arch = "wasm32"))]
{
use crate::gpu_backend::{active_gpu, load_prefs};
let gpu = active_gpu();
let prefs = load_prefs();
let or_unknown = |s: &str| {
if s.is_empty() {
"Unknown".to_string()
} else {
s.to_string()
}
};
out.push_str(&format!(
"\n[Graphics]\nGPU: {}\nDriver: {}\nBackend: {}\nSelected by: {}\n\
Below WebGPU baseline: {}\nWGPU_BACKEND: {}\n\
Saved options: backend={}, compat renderer={}, OpenGL on older GPUs={}\n",
or_unknown(gpu.adapter.as_deref().unwrap_or("")),
or_unknown(&gpu.driver),
or_unknown(gpu.backend.as_deref().unwrap_or("")),
or_unknown(&gpu.origin),
if gpu.legacy { "yes" } else { "no" },
std::env::var("WGPU_BACKEND").unwrap_or_else(|_| "(unset)".into()),
prefs.backend.as_str().unwrap_or("auto"),
prefs.compat_renderer,
prefs.legacy_gl,
));
}
out
}
/// Build card text: the metadata suffix without its leading `+`
/// (`194.gef189d77`, `gef189d77.dirty`), or "Release" for a clean build on
/// the release tag.
@@ -137,6 +223,19 @@ pub fn view_window(
.spacing(8)
.width(content_width);
let row_width = if matches!(sizing.width, Length::Fill) {
Fill
} else {
Length::Fixed(148.0 * 3.0 + 16.0)
};
let system = column![
info_row(t!("CPU"), crate::sysinfo::cpu_summary(), row_width),
info_row(t!("RAM"), crate::sysinfo::ram_summary(), row_width),
info_row(t!("GPU (backend)"), gpu_summary(), row_width),
]
.spacing(6)
.width(content_width);
let copy = dialog_button(t!("Copy Info"), Message::AboutCopyInfo, true);
container(
@@ -144,6 +243,7 @@ pub fn view_window(
hero,
metadata,
build_info,
system,
row![Space::new().width(content_width), copy]
.width(sizing.width)
.align_y(iced::Center),
+10 -1
View File
@@ -1,3 +1,5 @@
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod graphics;
pub(crate) mod spacemouse;
use crate::app::config::UiThemeConfig;
use crate::app::settings;
@@ -35,6 +37,7 @@ pub enum OptionsTab {
Modeling,
Selection,
UserPreferences,
Graphics,
}
/// Application preferences the dialog reads that are plain scalars on the app.
@@ -201,6 +204,7 @@ pub fn view_window<'a>(
selection: SelectionPrefs,
prefs: AppPrefs,
spacemouse: Element<'a, Message>,
graphics: Element<'a, Message>,
snap_angle_input: &'a str,
zoom_factor_input: &'a str,
drawing_prefs: DrawingPrefs,
@@ -1675,6 +1679,7 @@ pub fn view_window<'a>(
OptionsTab::Drafting => drafting.into(),
OptionsTab::Modeling => modeling.into(),
OptionsTab::UserPreferences => user_prefs.into(),
OptionsTab::Graphics => graphics,
};
// A vertical rail rather than a horizontal strip: the tab names are
@@ -1697,7 +1702,11 @@ pub fn view_window<'a>(
tab_button(crate::t!("3D Modeling"), OptionsTab::Modeling),
tab_button(crate::t!("Selection"), OptionsTab::Selection),
tab_button(crate::t!("User Preferences"), OptionsTab::UserPreferences),
]
];
// The web build has no backend to choose, so it has no Graphics page.
#[cfg(not(target_arch = "wasm32"))]
let tabs = tabs.push(tab_button(crate::t!("Graphics"), OptionsTab::Graphics));
let tabs = tabs
.spacing(2)
.width(iced::Length::Fixed(TAB_RAIL_WIDTH));
+102
View File
@@ -0,0 +1,102 @@
//! Options > Graphics: which graphics backend the next launch uses.
//!
//! Everything here is read before the application boots (the backend is
//! chosen first), so changes are saved at once and apply on restart rather
//! than going through OK / Apply.
use crate::app::Message;
use crate::gpu_backend::{ActiveGpu, BackendChoice, GraphicsPrefs};
use iced::widget::{checkbox, column, pick_list, row, text, Space};
use iced::{Element, Fill};
fn current_row(label: std::borrow::Cow<'static, str>, value: String) -> Element<'static, Message> {
row![
text(label).size(12).width(150),
text(value).size(12).width(Fill),
]
.spacing(10)
.into()
}
fn unknown(s: &str) -> String {
if s.is_empty() {
crate::t!("Unknown").into_owned()
} else {
s.to_string()
}
}
pub(crate) fn view(prefs: GraphicsPrefs, active: ActiveGpu) -> Element<'static, Message> {
let backend = pick_list(
Some(prefs.backend),
BackendChoice::available(),
|choice: &BackendChoice| crate::t!(choice.label()).into_owned(),
)
.on_select(Message::GraphicsBackendChanged)
.width(260);
column![
text(crate::t!("Graphics")).size(15),
Space::new().height(6),
text(crate::t!(
"Saved immediately. Changes take effect the next time Open CAD Studio starts."
))
.size(11)
.width(Fill),
Space::new().height(16),
text(crate::t!("This session")).size(13),
Space::new().height(8),
current_row(
crate::t!("GPU"),
unknown(active.adapter.as_deref().unwrap_or(""))
),
Space::new().height(4),
current_row(crate::t!("Driver"), unknown(&active.driver)),
Space::new().height(4),
current_row(
crate::t!("Backend"),
unknown(active.backend.as_deref().unwrap_or(""))
),
Space::new().height(4),
current_row(crate::t!("Selected by"), unknown(&active.origin)),
Space::new().height(20),
text(crate::t!("Next launch")).size(13),
Space::new().height(8),
row![text(crate::t!("Graphics backend")).size(12).width(150), backend]
.spacing(10)
.align_y(iced::Center),
Space::new().height(12),
row![
checkbox(prefs.compat_renderer)
.on_toggle(Message::GraphicsCompatToggled)
.size(15),
text(crate::t!(
"Use the compatibility renderer (for GPUs without shader storage buffers)"
))
.size(12),
]
.spacing(8)
.align_y(iced::Center),
Space::new().height(10),
row![
checkbox(prefs.legacy_gl)
.on_toggle(Message::GraphicsLegacyGlToggled)
.size(15),
text(crate::t!(
"Prefer OpenGL on older GPUs (Windows, automatic backend only)"
))
.size(12),
]
.spacing(8)
.align_y(iced::Center),
Space::new().height(16),
text(crate::t!(
"If a pinned backend fails to start, the next launch falls back to automatic selection. \
The --backend option and the WGPU_BACKEND variable override this page."
))
.size(11)
.width(Fill),
]
.spacing(0)
.into()
}