Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb11b1f828 | ||
|
|
89005f718e |
No files matched your search
Generated
+1
-1
@@ -1048,7 +1048,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.3.4"
|
version = "0.3.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.3.4"
|
version = "0.3.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux"
|
description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux"
|
||||||
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04).
|
Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04).
|
||||||
|
|
||||||
### Highlights:
|
### Highlights:
|
||||||
|
- **Stabiler Start**: Kein Absturz mehr, wenn die Grafikkarte nicht nutzbar ist (z. B. fehlende Rechte auf /dev/dri); Software-Renderer als Ausweichlösung und verständliche Fehlermeldung
|
||||||
- **Wichtige Fehlerbehebung**: Nach einem Neustart auf einer späteren Seite konnte Weiterschreiben den bisherigen Inhalt dieser Seite überschreiben (betrifft v0.2.3 – v0.3.2)
|
- **Wichtige Fehlerbehebung**: Nach einem Neustart auf einer späteren Seite konnte Weiterschreiben den bisherigen Inhalt dieser Seite überschreiben (betrifft v0.2.3 – v0.3.2)
|
||||||
- **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert
|
- **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert
|
||||||
- **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl mit Vorschaubildern, größere Bedienelemente in allen Popovern
|
- **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl mit Vorschaubildern, größere Bedienelemente in allen Popovern
|
||||||
|
|||||||
+92
-27
@@ -87,8 +87,35 @@ pub fn spline_vertices_to_ndc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verwaltet den GPU-Zustand, Surface, Pipelines und Buffer für das Inking.
|
/// Verwaltet den GPU-Zustand, Surface, Pipelines und Buffer für das Inking.
|
||||||
|
/// Meldung, wenn kein Grafikadapter das Fenster darstellen kann.
|
||||||
|
pub const NO_USABLE_GPU_MESSAGE: &str = "Keine nutzbare Grafikschnittstelle gefunden.\n\
|
||||||
|
Häufigste Ursache: Der Benutzer darf nicht auf die Grafikkarte zugreifen (/dev/dri).\n\
|
||||||
|
Prüfen mit `ls -l /dev/dri` und `groups`; ggf. `sudo usermod -aG render,video $USER`\n\
|
||||||
|
und neu anmelden. Alternativ einen Software-Renderer installieren (Paket mesa-vulkan-drivers)\n\
|
||||||
|
oder die Schnittstelle festlegen, z. B. `WGPU_BACKEND=vulkan ink-notes`.";
|
||||||
|
|
||||||
|
/// Wählt den Darstellungsmodus: Mailbox (geringste Latenz), sonst Fifo, sonst den ersten
|
||||||
|
/// angebotenen. `None`, wenn keiner angeboten wird. `INK_FORCE_FIFO` erzwingt Fifo.
|
||||||
|
pub fn choose_present_mode(available: &[wgpu::PresentMode]) -> Option<wgpu::PresentMode> {
|
||||||
|
let force_fifo = std::env::var("INK_FORCE_FIFO").is_ok();
|
||||||
|
let preferred: &[wgpu::PresentMode] = if force_fifo {
|
||||||
|
&[wgpu::PresentMode::Fifo]
|
||||||
|
} else {
|
||||||
|
&[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Fifo]
|
||||||
|
};
|
||||||
|
preferred
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|m| available.contains(m))
|
||||||
|
.or_else(|| available.first().copied())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CanvasState {
|
pub struct CanvasState {
|
||||||
pub surface: wgpu::Surface<'static>,
|
pub surface: wgpu::Surface<'static>,
|
||||||
|
/// Verwendeter Grafikadapter (für erneute Abfrage der Surface-Fähigkeiten bei Größenänderung)
|
||||||
|
adapter: wgpu::Adapter,
|
||||||
|
/// Meldung „keine Darstellungsmodi“ wurde bereits ausgegeben
|
||||||
|
presentation_warned: bool,
|
||||||
pub device: wgpu::Device,
|
pub device: wgpu::Device,
|
||||||
pub queue: wgpu::Queue,
|
pub queue: wgpu::Queue,
|
||||||
pub config: wgpu::SurfaceConfiguration,
|
pub config: wgpu::SurfaceConfiguration,
|
||||||
@@ -149,23 +176,47 @@ impl CanvasState {
|
|||||||
let viewport =
|
let viewport =
|
||||||
PageViewport::calculate(size.width as f32, size.height as f32, Self::VIEWPORT_MARGIN);
|
PageViewport::calculate(size.width as f32, size.height as f32, Self::VIEWPORT_MARGIN);
|
||||||
|
|
||||||
// 1. WGPU Instance & Surface
|
// 1. WGPU Instance & Surface (Grafikschnittstelle per WGPU_BACKEND wählbar, z. B. "vulkan")
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
backends: wgpu::Backends::all(),
|
backends: wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let surface = instance.create_surface(window.clone())?;
|
let surface = instance.create_surface(window.clone())?;
|
||||||
|
|
||||||
// 2. Adapter auswählen (hohe Performance bevorzugt)
|
// 2. Adapter auswählen: zuerst die GPU, dann der Software-Renderer (z. B. lavapipe).
|
||||||
let adapter = instance
|
// Ein Adapter taugt nur, wenn er für dieses Fenster Formate und Darstellungsmodi
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
// anbietet – fehlt z. B. der Zugriff auf /dev/dri, ist die Liste leer.
|
||||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
let mut chosen = None;
|
||||||
compatible_surface: Some(&surface),
|
for force_fallback_adapter in [false, true] {
|
||||||
force_fallback_adapter: false,
|
let Some(adapter) = instance
|
||||||
})
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
.await
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||||
.ok_or("Kein passender GPU-Adapter gefunden")?;
|
compatible_surface: Some(&surface),
|
||||||
|
force_fallback_adapter,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let info = adapter.get_info();
|
||||||
|
let caps = surface.get_capabilities(&adapter);
|
||||||
|
match choose_present_mode(&caps.present_modes) {
|
||||||
|
Some(mode) if !caps.formats.is_empty() && !caps.alpha_modes.is_empty() => {
|
||||||
|
eprintln!(
|
||||||
|
"[Grafik] Adapter: {} ({:?}), Darstellungsmodus {:?}",
|
||||||
|
info.name, info.backend, mode
|
||||||
|
);
|
||||||
|
chosen = Some((adapter, caps, mode));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => eprintln!(
|
||||||
|
"[Grafik] Adapter {} ({:?}) bietet für das Fenster keine Darstellung an, versuche den nächsten.",
|
||||||
|
info.name, info.backend
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (adapter, surface_caps, present_mode) = chosen.ok_or(NO_USABLE_GPU_MESSAGE)?;
|
||||||
|
|
||||||
// 3. Device & Queue erstellen
|
// 3. Device & Queue erstellen
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
@@ -179,8 +230,7 @@ impl CanvasState {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// 4. Zero-Latency-Pufferung: Mailbox mit Fallback Fifo & Frame-Latency 1
|
// 4. Zero-Latency-Pufferung (Mailbox, sonst Fifo) & Frame-Latency 1
|
||||||
let surface_caps = surface.get_capabilities(&adapter);
|
|
||||||
let surface_format = surface_caps
|
let surface_format = surface_caps
|
||||||
.formats
|
.formats
|
||||||
.iter()
|
.iter()
|
||||||
@@ -188,20 +238,6 @@ impl CanvasState {
|
|||||||
.find(|f| f.is_srgb())
|
.find(|f| f.is_srgb())
|
||||||
.unwrap_or(surface_caps.formats[0]);
|
.unwrap_or(surface_caps.formats[0]);
|
||||||
|
|
||||||
let present_mode = if std::env::var("INK_FORCE_FIFO").is_ok() {
|
|
||||||
eprintln!("Erzwinge PresentMode::Fifo (via INK_FORCE_FIFO)");
|
|
||||||
wgpu::PresentMode::Fifo
|
|
||||||
} else if surface_caps
|
|
||||||
.present_modes
|
|
||||||
.contains(&wgpu::PresentMode::Mailbox)
|
|
||||||
{
|
|
||||||
eprintln!("Verwende Zero-Latency PresentMode::Mailbox");
|
|
||||||
wgpu::PresentMode::Mailbox
|
|
||||||
} else {
|
|
||||||
eprintln!("Mailbox nicht verfügbar, Fallback auf PresentMode::Fifo");
|
|
||||||
wgpu::PresentMode::Fifo
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = wgpu::SurfaceConfiguration {
|
let config = wgpu::SurfaceConfiguration {
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
@@ -369,6 +405,8 @@ impl CanvasState {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
surface,
|
surface,
|
||||||
|
adapter,
|
||||||
|
presentation_warned: false,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
config,
|
config,
|
||||||
@@ -452,6 +490,23 @@ impl CanvasState {
|
|||||||
self.size = new_size;
|
self.size = new_size;
|
||||||
self.config.width = new_size.width;
|
self.config.width = new_size.width;
|
||||||
self.config.height = new_size.height;
|
self.config.height = new_size.height;
|
||||||
|
// Fähigkeiten neu abfragen: Ein nicht (mehr) angebotener Modus führt in wgpu zum Absturz
|
||||||
|
let caps = self.surface.get_capabilities(&self.adapter);
|
||||||
|
if !caps.present_modes.contains(&self.config.present_mode) {
|
||||||
|
match choose_present_mode(&caps.present_modes) {
|
||||||
|
Some(mode) => self.config.present_mode = mode,
|
||||||
|
None => {
|
||||||
|
if !self.presentation_warned {
|
||||||
|
eprintln!(
|
||||||
|
"[Grafik] Fenster kann derzeit nicht dargestellt werden (keine Darstellungsmodi). {}",
|
||||||
|
NO_USABLE_GPU_MESSAGE
|
||||||
|
);
|
||||||
|
self.presentation_warned = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
self.recalculate_viewport();
|
self.recalculate_viewport();
|
||||||
}
|
}
|
||||||
@@ -858,6 +913,16 @@ impl CanvasState {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_choose_present_mode() {
|
||||||
|
use wgpu::PresentMode::{Fifo, Immediate, Mailbox};
|
||||||
|
assert_eq!(choose_present_mode(&[Fifo, Mailbox]), Some(Mailbox));
|
||||||
|
assert_eq!(choose_present_mode(&[Fifo, Immediate]), Some(Fifo));
|
||||||
|
assert_eq!(choose_present_mode(&[Immediate]), Some(Immediate));
|
||||||
|
// Keine Darstellungsmodi (z. B. kein Zugriff auf /dev/dri): kein Absturz, sondern None
|
||||||
|
assert_eq!(choose_present_mode(&[]), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pixel_to_ndc_mapping() {
|
fn test_pixel_to_ndc_mapping() {
|
||||||
let width = 800.0;
|
let width = 800.0;
|
||||||
|
|||||||
+7
-1
@@ -145,7 +145,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build(&event_loop)?,
|
.build(&event_loop)?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut canvas = pollster::block_on(CanvasState::new(window.clone()))?;
|
let mut canvas = match pollster::block_on(CanvasState::new(window.clone())) {
|
||||||
|
Ok(canvas) => canvas,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("[Grafik-Fehler] {err}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut tablet_manager = match octotablet::builder::Builder::default().build_shared(&window) {
|
let mut tablet_manager = match octotablet::builder::Builder::default().build_shared(&window) {
|
||||||
Ok(mgr) => {
|
Ok(mgr) => {
|
||||||
|
|||||||
Reference in new issue
Block a user