feat(terminal): recover webgl renderer context

This commit is contained in:
zhangheng
2026-06-24 13:47:31 +08:00
parent e66dcb6f80
commit f4c919de40
4 changed files with 214 additions and 9 deletions

View File

@@ -27,8 +27,10 @@ use super::mouse::{
};
use super::protocol::{ClientTerminalMessage, ServerTerminalMessage};
use super::renderer::{
CanvasDomState, CanvasPlanInput, CanvasSurfaceFrame, RenderCanvasSurfaceRequest,
WebGlRendererState, canvas_render_plan, render_canvas_surface, render_webgl,
CanvasDomState, CanvasPlanInput, CanvasSurfaceFrame, CanvasSurfaceMode,
RenderCanvasSurfaceRequest, WebGlContextLifecycle, WebGlRendererState,
attach_webgl_context_lifecycle, canvas_render_plan, render_canvas_surface, render_webgl,
set_canvas_surface_mode, webgl_surface_mode,
};
use super::scrollback::ViewMode;
use super::selection::{Selection, SelectionRect, selection_rects};
@@ -472,6 +474,7 @@ pub fn TerminalPanel(
let terminal_ref = NodeRef::<html::Div>::new();
let ime_ref = NodeRef::<html::Textarea>::new();
let canvas_ref = NodeRef::<html::Canvas>::new();
let canvas_fallback_ref = NodeRef::<html::Canvas>::new();
let measure_ref = NodeRef::<html::Span>::new();
let connection_status = RwSignal::new("Connecting".to_owned());
let ws_signal = RwSignal::new(None::<WebSocket>);
@@ -525,8 +528,11 @@ pub fn TerminalPanel(
Rc::new(std::cell::Cell::new(None));
let canvas_state: Rc<std::cell::RefCell<Option<CanvasDomState>>> =
Rc::new(std::cell::RefCell::new(None));
let canvas_fallback_state: Rc<std::cell::RefCell<Option<CanvasDomState>>> =
Rc::new(std::cell::RefCell::new(None));
let webgl_state: Rc<std::cell::RefCell<Option<WebGlRendererState>>> =
Rc::new(std::cell::RefCell::new(None));
let webgl_context_lost: Rc<std::cell::Cell<bool>> = Rc::new(std::cell::Cell::new(false));
handle.serialize_text_fn.set_value(Some(Arc::new(move || {
core_signal
@@ -623,6 +629,34 @@ pub fn TerminalPanel(
}
});
let lifecycle_webgl_state = webgl_state.clone();
let lifecycle_context_lost = webgl_context_lost.clone();
Effect::new(move |prev: Option<Option<WebGlContextLifecycle>>| {
drop(prev);
let renderer = options_signal.get().renderer;
if renderer != Renderer::WebGl {
lifecycle_context_lost.set(false);
lifecycle_webgl_state.borrow_mut().take();
return None;
}
let Some(canvas) = canvas_ref.get() else {
return None;
};
let Some(canvas_el) = canvas.dyn_ref::<HtmlCanvasElement>() else {
return None;
};
attach_webgl_context_lifecycle(
canvas_el,
lifecycle_webgl_state.clone(),
lifecycle_context_lost.clone(),
move || {
let _ = render_tick.try_update(|t| *t += 1);
},
)
});
Effect::new(move |_| {
let Some(on_selection_change) = on_selection_change else {
return;
@@ -815,6 +849,9 @@ pub fn TerminalPanel(
let Some(canvas) = canvas_ref.get() else {
return;
};
let Some(fallback_canvas) = canvas_fallback_ref.get() else {
return;
};
let Some(element) = terminal_ref.get() else {
return;
};
@@ -827,6 +864,9 @@ pub fn TerminalPanel(
let Some(canvas_el) = canvas.dyn_ref::<HtmlCanvasElement>() else {
return;
};
let Some(fallback_canvas_el) = fallback_canvas.dyn_ref::<HtmlCanvasElement>() else {
return;
};
let is_live = view_mode.get().is_live();
let st = scroll_top.get();
@@ -881,15 +921,35 @@ pub fn TerminalPanel(
};
match renderer {
Renderer::WebGl => {
if render_webgl(canvas_el, frame, &canvas_state, &webgl_state).is_err() {
render_canvas_surface(RenderCanvasSurfaceRequest {
if render_webgl(
canvas_el,
frame,
&canvas_state,
&webgl_state,
&webgl_context_lost,
)
.is_ok()
{
set_canvas_surface_mode(
canvas_el,
fallback_canvas_el,
webgl_surface_mode(true),
);
} else {
set_canvas_surface_mode(
canvas_el,
fallback_canvas_el,
webgl_surface_mode(false),
);
render_canvas_surface(RenderCanvasSurfaceRequest {
canvas_el: fallback_canvas_el,
frame,
canvas_state: &canvas_state,
canvas_state: &canvas_fallback_state,
});
}
}
Renderer::Canvas => {
set_canvas_surface_mode(canvas_el, fallback_canvas_el, CanvasSurfaceMode::WebGl);
render_canvas_surface(RenderCanvasSurfaceRequest {
canvas_el,
frame,
@@ -1885,6 +1945,11 @@ pub fn TerminalPanel(
node_ref=canvas_ref
class="terminal-canvas"
></canvas>
<canvas
node_ref=canvas_fallback_ref
class="terminal-canvas"
hidden
></canvas>
{move || {
// Canvas-backed renderers: only the scroll sizer
// and selection overlay are DOM; text/backgrounds