fix(renderer): defer desktop gl startup
This commit is contained in:
parent
1b97bdea45
commit
4249721d98
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
git_jobs, menu, persistence, settings_io, window_state, DesktopApp, INITIAL_VIEWPORT_H,
|
||||
INITIAL_VIEWPORT_W,
|
||||
};
|
||||
use op_host_native::{NativeBackend, SharedSkiaContext};
|
||||
use op_host_native::{NativeBackend, ProviderError, SharedSkiaContext, SharedSkiaError};
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::{
|
||||
|
|
@ -46,7 +46,8 @@ impl ApplicationHandler for DesktopApp {
|
|||
.with_inner_size(winit::dpi::LogicalSize::new(
|
||||
INITIAL_VIEWPORT_W as u32,
|
||||
INITIAL_VIEWPORT_H as u32,
|
||||
));
|
||||
))
|
||||
.with_min_inner_size(winit::dpi::LogicalSize::new(640.0f64, 400.0f64));
|
||||
// Hide the title bar but keep the platform's own window
|
||||
// controls — the Electron `titleBarStyle: 'hidden'` recipe.
|
||||
//
|
||||
|
|
@ -95,18 +96,6 @@ impl ApplicationHandler for DesktopApp {
|
|||
|
||||
let dpi = window.scale_factor() as f32;
|
||||
self.dpi = dpi;
|
||||
match SharedSkiaContext::new_desktop(&window) {
|
||||
Ok(ctx) => {
|
||||
self.ctx = Some(ctx);
|
||||
self.backend = Some(NativeBackend::with_dpi(dpi));
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("openpencil-desktop: SharedSkiaContext::new_desktop failed: {err}");
|
||||
self.error = Some(err);
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Build the curved-arrow rotate cursor once and cache it.
|
||||
let (rgba, w, h, hx, hy) = cursor_icon::make_rotate_cursor_rgba();
|
||||
match winit::window::CustomCursor::from_rgba(rgba, w, h, hx, hy) {
|
||||
|
|
@ -223,15 +212,17 @@ impl ApplicationHandler for DesktopApp {
|
|||
// first paint so the launch document shows immediately.
|
||||
self.drain_opened_files();
|
||||
|
||||
if let (Some(ctx), Some(backend)) = (self.ctx.as_mut(), self.backend.as_mut()) {
|
||||
frame::paint(
|
||||
ctx,
|
||||
backend,
|
||||
&mut self.host,
|
||||
self.viewport_width,
|
||||
self.viewport_height,
|
||||
self.dpi,
|
||||
);
|
||||
if self.try_init_render_context(event_loop) {
|
||||
if let (Some(ctx), Some(backend)) = (self.ctx.as_mut(), self.backend.as_mut()) {
|
||||
frame::paint(
|
||||
ctx,
|
||||
backend,
|
||||
&mut self.host,
|
||||
self.viewport_width,
|
||||
self.viewport_height,
|
||||
self.dpi,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The auto-update probe spawned in `new()` is likely still
|
||||
|
|
@ -279,6 +270,8 @@ impl ApplicationHandler for DesktopApp {
|
|||
self.error = Some(err);
|
||||
event_loop.exit();
|
||||
}
|
||||
} else {
|
||||
self.try_init_render_context(event_loop);
|
||||
}
|
||||
self.viewport_width = size.width as f32 / self.dpi;
|
||||
self.viewport_height = size.height as f32 / self.dpi;
|
||||
|
|
@ -355,6 +348,13 @@ impl ApplicationHandler for DesktopApp {
|
|||
self.request_redraw(true);
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if (self.ctx.is_none() || self.backend.is_none())
|
||||
&& !self.try_init_render_context(event_loop)
|
||||
{
|
||||
self.redraw_pending = false;
|
||||
self.redraw_dirty = true;
|
||||
return;
|
||||
}
|
||||
if chat_session::drain_new_chat_request(
|
||||
&mut self.host,
|
||||
&mut self.current_chat,
|
||||
|
|
@ -874,3 +874,78 @@ impl ApplicationHandler for DesktopApp {
|
|||
self.window.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_surface_not_ready(err: &SharedSkiaError) -> bool {
|
||||
matches!(
|
||||
err,
|
||||
SharedSkiaError::Provider(ProviderError::SurfaceNotReady { .. })
|
||||
)
|
||||
}
|
||||
|
||||
impl DesktopApp {
|
||||
fn try_init_render_context(&mut self, event_loop: &ActiveEventLoop) -> bool {
|
||||
if self.ctx.is_some() && self.backend.is_some() {
|
||||
return true;
|
||||
}
|
||||
let Some(window) = self.window.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let size = window.inner_size();
|
||||
if size.width <= 1 || size.height <= 1 {
|
||||
self.defer_render_context_init(event_loop);
|
||||
return false;
|
||||
}
|
||||
|
||||
let dpi = window.scale_factor() as f32;
|
||||
match SharedSkiaContext::new_desktop(window) {
|
||||
Ok(ctx) => {
|
||||
self.dpi = dpi;
|
||||
self.ctx = Some(ctx);
|
||||
self.backend = Some(NativeBackend::with_dpi(dpi));
|
||||
true
|
||||
}
|
||||
Err(err) if render_surface_not_ready(&err) => {
|
||||
self.defer_render_context_init(event_loop);
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("openpencil-desktop: SharedSkiaContext::new_desktop failed: {err}");
|
||||
self.error = Some(err);
|
||||
event_loop.exit();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn defer_render_context_init(&self, event_loop: &ActiveEventLoop) {
|
||||
if let Some(window) = self.window.as_ref() {
|
||||
let _ = window.request_inner_size(winit::dpi::LogicalSize::new(
|
||||
INITIAL_VIEWPORT_W as f64,
|
||||
INITIAL_VIEWPORT_H as f64,
|
||||
));
|
||||
}
|
||||
event_loop.set_control_flow(ControlFlow::WaitUntil(
|
||||
Instant::now() + Duration::from_millis(50),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn render_init_retries_only_surface_not_ready() {
|
||||
let not_ready = op_host_native::SharedSkiaError::Provider(
|
||||
op_host_native::ProviderError::SurfaceNotReady {
|
||||
width: 1,
|
||||
height: 1,
|
||||
},
|
||||
);
|
||||
assert!(render_surface_not_ready(¬_ready));
|
||||
|
||||
assert!(!render_surface_not_ready(
|
||||
&op_host_native::SharedSkiaError::GlInterface
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -825,7 +825,13 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
let initial_file = initial_file_from_argv();
|
||||
let event_loop = match EventLoop::new() {
|
||||
let mut event_loop_builder = EventLoop::builder();
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
|
||||
event_loop_builder.with_activation_policy(ActivationPolicy::Regular);
|
||||
}
|
||||
let event_loop = match event_loop_builder.build() {
|
||||
Ok(el) => el,
|
||||
Err(err) => {
|
||||
eprintln!("openpencil-desktop: EventLoop::new failed: {err}");
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ pub enum ProviderError {
|
|||
/// cross-platform uniformity (each backend uses different error types).
|
||||
#[error("GL context provider failed: {0}")]
|
||||
Failure(String),
|
||||
/// The platform has created a window, but its content surface is
|
||||
/// still the placeholder startup size. Retrying after the first
|
||||
/// real resize/redraw avoids entering native GL with an invalid
|
||||
/// drawable.
|
||||
#[error("GL window surface is not ready: {width}x{height}")]
|
||||
SurfaceNotReady { width: u32, height: u32 },
|
||||
}
|
||||
|
||||
impl ProviderError {
|
||||
|
|
@ -194,11 +200,11 @@ impl GlutinProvider {
|
|||
// `glutin_winit::GlWindow::build_surface_attributes` does
|
||||
// internally; we inline it so the crate stays off
|
||||
// `glutin-winit` (which is pinned to the upstream `winit`
|
||||
// package). Sizes are clamped to ≥ 1 — a zero-area surface
|
||||
// is rejected by every GL backend.
|
||||
// package). During macOS startup, winit can briefly expose a
|
||||
// placeholder 1×1 drawable; reject that state before entering
|
||||
// native GL so the caller can retry after the real resize.
|
||||
let inner = window.inner_size();
|
||||
let surface_width = std::num::NonZeroU32::new(inner.width.max(1)).unwrap();
|
||||
let surface_height = std::num::NonZeroU32::new(inner.height.max(1)).unwrap();
|
||||
let (surface_width, surface_height) = window_surface_size(inner)?;
|
||||
let surface_attrs =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(raw_window_handle, surface_width, surface_height);
|
||||
|
|
@ -230,6 +236,21 @@ impl GlutinProvider {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
fn window_surface_size(
|
||||
inner: winit::dpi::PhysicalSize<u32>,
|
||||
) -> ProviderResult<(std::num::NonZeroU32, std::num::NonZeroU32)> {
|
||||
if inner.width <= 1 || inner.height <= 1 {
|
||||
return Err(ProviderError::SurfaceNotReady {
|
||||
width: inner.width,
|
||||
height: inner.height,
|
||||
});
|
||||
}
|
||||
let width = std::num::NonZeroU32::new(inner.width).expect("surface width is > 1");
|
||||
let height = std::num::NonZeroU32::new(inner.height).expect("surface height is > 1");
|
||||
Ok((width, height))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn pick_display_api(
|
||||
_raw: raw_window_handle::RawWindowHandle,
|
||||
|
|
@ -389,3 +410,32 @@ impl GlContextProvider for AndroidEglProvider {
|
|||
unimplemented!("Step 1f")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[test]
|
||||
fn window_surface_size_rejects_tiny_startup_extents() {
|
||||
let err = window_surface_size(winit::dpi::PhysicalSize::new(1, 1)).unwrap_err();
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
ProviderError::SurfaceNotReady {
|
||||
width: 1,
|
||||
height: 1
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[test]
|
||||
fn window_surface_size_keeps_usable_extents() {
|
||||
let (width, height) =
|
||||
window_surface_size(winit::dpi::PhysicalSize::new(1440, 900)).unwrap();
|
||||
|
||||
assert_eq!(width.get(), 1440);
|
||||
assert_eq!(height.get(), 900);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue