forked from 1b-agent/opencadstudio-upstream
Older GPUs without usable DirectX 12 Feature Level 12_0 or working Vulkan support (e.g. legacy Intel iGPUs and other old parts), and old GLES drivers whose shader compiler rejects iced's own shaders, crash at startup or on the first drawing viewport: StagingBelt aborts in Buffer::get_mapped_range (panic=abort, silent exit) on the DX12 path, and shader compilation fails inside iced's gradient pipeline on the GLES path. Selecting a working backend via WGPU_BACKEND before iced boots avoids both classes. - Probe each candidate backend (Windows: dx12,vulkan,gl; Linux: vulkan,gl) in an isolated --gpu-probe child: device request, staging-style mapped upload, render pipeline creation (its fragment shader reads a private vec4 array, catching old-Mesa compile failures), offscreen render and readback. First fully-passing backend wins; explicit --backend / WGPU_BACKEND / --safe-mode always win, and Windows keeps preferring DX12/Vulkan (#55). - Software adapters (WARP/llvmpipe/SwiftShader) are the last resort before a best-effort GL attempt: slow but alive. - The crash sentinel names the attempted backend so only implicated backends are skipped next launch. - Winning probes report adapter limits; GPUs without shader storage buffers get the packed compatibility renderer automatically, with a startup command-line notice so the switch is never silent.
149 lines
6.6 KiB
Rust
149 lines
6.6 KiB
Rust
//! Command-line interface.
|
|
//!
|
|
//! Parsing lives here; `main` interprets the result. Four run modes split out
|
|
//! of the parsed args:
|
|
//! - `--mcp` local stdio MCP server for AI clients
|
|
//! - `--serve` headless JSON automation server (see `app::serve`)
|
|
//! - `--export IN OUT` one-shot headless format conversion, then exit
|
|
//! - otherwise launch the GUI editor, configured via [`GuiConfig`]
|
|
//!
|
|
//! GUI-only options (open-file, `--new`, `--read-only`, `--script`) are stashed
|
|
//! in [`GUI_CONFIG`] for `app::boot` to read, since the iced daemon boots with
|
|
//! no arguments of its own.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::OnceLock;
|
|
|
|
use clap::Parser;
|
|
|
|
/// Open CAD Studio command-line options.
|
|
#[derive(Parser, Debug, Default)]
|
|
#[command(
|
|
name = "OpenCADStudio",
|
|
version = env!("OCS_FULL_VERSION"),
|
|
long_version = concat!(
|
|
env!("OCS_FULL_VERSION"),
|
|
"\nrevision: ", env!("OCS_GIT_REV"),
|
|
"\ncommit date: ", env!("OCS_COMMIT_DATE"),
|
|
"\nprofile: ", env!("OCS_BUILD_PROFILE"),
|
|
"\nfeatures: ", env!("OCS_BUILD_FEATURES"),
|
|
),
|
|
about = crate::t!("Open CAD Studio — 2D/3D CAD editor").into_owned(),
|
|
long_about = None,
|
|
)]
|
|
pub struct Cli {
|
|
/// CAD files to open at startup (.dwg / .dxf). Also how the OS file
|
|
/// association launches us when drawings are double-clicked — selecting
|
|
/// several hands them all to one launch, so this takes a list.
|
|
#[arg(help = crate::t!("CAD files to open at startup (.dwg / .dxf).").into_owned(), long_help = None)]
|
|
pub files: Vec<PathBuf>,
|
|
|
|
/// Start with a new empty drawing, ignoring any file argument.
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Start with a new empty drawing.").into_owned(), long_help = None)]
|
|
pub new: bool,
|
|
|
|
/// Always start a new editor process, even when one is already running.
|
|
/// Without this, opening a drawing hands it to the running editor as a tab.
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Start a new editor process.").into_owned(), long_help = None)]
|
|
pub new_instance: bool,
|
|
|
|
/// Open read-only: editing is allowed but saving is disabled.
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Open read-only: saving is disabled.").into_owned(), long_help = None)]
|
|
pub read_only: bool,
|
|
|
|
/// Restrict the GPU backend (e.g. dx12, vulkan, gl, metal). Sets WGPU_BACKEND.
|
|
#[arg(long, value_name = "BACKEND")]
|
|
#[arg(help = crate::t!("GPU backend (dx12, vulkan, gl, metal). Sets WGPU_BACKEND.").into_owned(), long_help = None)]
|
|
pub backend: Option<String>,
|
|
|
|
/// Safe mode: force the GL backend, for flaky/hybrid GPU drivers.
|
|
#[arg(long, visible_alias = "no-gpu")]
|
|
#[arg(help = crate::t!("Safe mode: use the GL backend.").into_owned(), long_help = None)]
|
|
pub safe_mode: bool,
|
|
|
|
/// Force the packed renderer path that avoids shader storage buffers.
|
|
/// Normally selected automatically for adapters with insufficient limits.
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Use the renderer for GPUs without shader storage buffers.").into_owned(), long_help = None)]
|
|
pub compat_renderer: bool,
|
|
|
|
/// Run the headless JSON automation server (stdin/stdout, or --port).
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Run the headless JSON automation server (stdin/stdout, or --port).").into_owned(), long_help = None)]
|
|
pub serve: bool,
|
|
|
|
/// Expose the running desktop editor to AI clients over MCP stdio.
|
|
#[arg(long)]
|
|
#[arg(help = crate::t!("Connect AI clients to the running editor over MCP stdio.").into_owned(), long_help = None)]
|
|
pub mcp: bool,
|
|
|
|
/// TCP port for --serve (defaults to stdin/stdout).
|
|
#[arg(long, value_name = "PORT")]
|
|
#[arg(help = crate::t!("TCP port for --serve (defaults to stdin/stdout).").into_owned(), long_help = None)]
|
|
pub port: Option<u16>,
|
|
|
|
/// Headless convert: read IN, write OUT (format from OUT's extension), exit.
|
|
#[arg(long, num_args = 2, value_names = ["IN", "OUT"])]
|
|
#[arg(help = crate::t!("Convert IN to OUT using the output file extension, then exit.").into_owned(), long_help = None)]
|
|
pub export: Option<Vec<PathBuf>>,
|
|
|
|
/// Run a command script at startup: one command line per line of FILE.
|
|
#[arg(long, value_name = "FILE")]
|
|
#[arg(help = crate::t!("Run a command script at startup, one command per line.").into_owned(), long_help = None)]
|
|
pub script: Option<PathBuf>,
|
|
|
|
/// Log level (error|warn|info|debug|trace). Also honours RUST_LOG.
|
|
#[arg(long, value_name = "LEVEL")]
|
|
#[arg(help = crate::t!("Log level (error|warn|info|debug|trace). Also reads RUST_LOG.").into_owned(), long_help = None)]
|
|
pub log: Option<String>,
|
|
|
|
/// Internal: probe one GPU backend offscreen (`dx12|vulkan|gl|sw`), print
|
|
/// one JSON line on success, then exit 0/1. Spawned by the GPU backend
|
|
/// resolver so a legacy-driver abort kills only the probe child, never
|
|
/// the editor itself.
|
|
#[arg(long, value_name = "BACKEND", hide = true)]
|
|
pub gpu_probe: Option<String>,
|
|
|
|
/// Internal: run as the plugin runner child process.
|
|
#[arg(long, value_names = ["SOCKET", "CDYLIB"], num_args = 2, hide = true)]
|
|
pub ocs_plugin_runner: Option<Vec<String>>,
|
|
|
|
/// Internal: write a DWG's embedded preview to a PNG for the OS file-manager
|
|
/// thumbnailer (`<IN> <OUT> <SIZE>`). Handled before the GUI starts.
|
|
#[arg(long, value_names = ["IN", "OUT", "SIZE"], num_args = 3, hide = true)]
|
|
pub dwg_thumbnail: Option<Vec<String>>,
|
|
}
|
|
|
|
/// GUI startup configuration, handed from `main` to `app::boot` out-of-band
|
|
/// because the iced daemon's boot closure takes no arguments.
|
|
#[derive(Debug, Default, Clone)]
|
|
pub struct GuiConfig {
|
|
/// Files to open on launch (empty for a blank session).
|
|
pub files: Vec<PathBuf>,
|
|
/// Open a fresh drawing tab on launch instead of the welcome screen.
|
|
pub new: bool,
|
|
/// Saving disabled for this session.
|
|
pub read_only: bool,
|
|
/// Force storage-buffer-free wire and hatch pipelines.
|
|
pub compat_renderer: bool,
|
|
/// Command lines to run once the editor is up.
|
|
pub script_lines: Vec<String>,
|
|
/// Why this launch switched graphics backend, if it did. Shown once
|
|
/// as a command-line warning so the switch is never silent.
|
|
pub gpu_fallback_notice: Option<String>,
|
|
/// The GPU probe found no shader storage buffers: the packed
|
|
/// compatibility renderer was enabled automatically for this session.
|
|
pub gpu_compat_auto: bool,
|
|
}
|
|
|
|
/// Set once by `main` before the GUI boots; read by `app::boot`.
|
|
pub static GUI_CONFIG: OnceLock<GuiConfig> = OnceLock::new();
|
|
|
|
/// The GUI config, or a default empty one if `main` never set it (e.g. tests).
|
|
pub fn gui_config() -> GuiConfig {
|
|
GUI_CONFIG.get().cloned().unwrap_or_default()
|
|
}
|