@@ -18,6 +18,7 @@ import {
1818import * as services from "@/store/services" ;
1919import { PLATFORM , PlatformMacOS } from "@/util/platformutil" ;
2020import { base64ToArray , fireAndForget } from "@/util/util" ;
21+ import { CanvasAddon } from "@xterm/addon-canvas" ;
2122import { SearchAddon } from "@xterm/addon-search" ;
2223import { SerializeAddon } from "@xterm/addon-serialize" ;
2324import { WebLinksAddon } from "@xterm/addon-web-links" ;
@@ -49,14 +50,14 @@ const MaxRepaintTransactionMs = 2000;
4950function detectWebGLSupport ( ) : boolean {
5051 try {
5152 const canvas = document . createElement ( "canvas" ) ;
52- const ctx = canvas . getContext ( "webgl " ) ;
53+ const ctx = canvas . getContext ( "webgl2 " ) ;
5354 return ! ! ctx ;
5455 } catch ( e ) {
5556 return false ;
5657 }
5758}
5859
59- const WebGLSupported = detectWebGLSupport ( ) ;
60+ export const WebGLSupported = detectWebGLSupport ( ) ;
6061let loggedWebGL = false ;
6162
6263type TermWrapOptions = {
@@ -84,7 +85,11 @@ export class TermWrap {
8485 multiInputCallback : ( data : string ) => void ;
8586 sendDataHandler : ( data : string ) => void ;
8687 onSearchResultsDidChange ?: ( result : { resultIndex : number ; resultCount : number } ) => void ;
87- private toDispose : TermTypes . IDisposable [ ] = [ ] ;
88+ toDispose : TermTypes . IDisposable [ ] = [ ] ;
89+ webglAddon : WebglAddon | null = null ;
90+ canvasAddon : CanvasAddon | null = null ;
91+ webglContextLossDisposable : TermTypes . IDisposable | null = null ;
92+ webglEnabledAtom : jotai . PrimitiveAtom < boolean > ;
8893 pasteActive : boolean = false ;
8994 lastUpdated : number ;
9095 promptMarkers : TermTypes . IMarker [ ] = [ ] ;
@@ -142,6 +147,7 @@ export class TermWrap {
142147 this . promptMarkers = [ ] ;
143148 this . shellIntegrationStatusAtom = jotai . atom ( null ) as jotai . PrimitiveAtom < ShellIntegrationStatus | null > ;
144149 this . lastCommandAtom = jotai . atom ( null ) as jotai . PrimitiveAtom < string | null > ;
150+ this . webglEnabledAtom = jotai . atom ( false ) as jotai . PrimitiveAtom < boolean > ;
145151 this . terminal = new Terminal ( options ) ;
146152 this . fitAddon = new FitAddon ( ) ;
147153 this . fitAddon . scrollbarWidth = 6 ; // this needs to match scrollbar width in term.scss
@@ -179,19 +185,7 @@ export class TermWrap {
179185 }
180186 )
181187 ) ;
182- if ( WebGLSupported && waveOptions . useWebGl ) {
183- const webglAddon = new WebglAddon ( ) ;
184- this . toDispose . push (
185- webglAddon . onContextLoss ( ( ) => {
186- webglAddon . dispose ( ) ;
187- } )
188- ) ;
189- this . terminal . loadAddon ( webglAddon ) ;
190- if ( ! loggedWebGL ) {
191- console . log ( "loaded webgl!" ) ;
192- loggedWebGL = true ;
193- }
194- }
188+ this . setTermRenderer ( WebGLSupported && waveOptions . useWebGl ? "webgl" : "canvas" ) ;
195189 // Register OSC handlers
196190 this . terminal . parser . registerOscHandler ( 7 , ( data : string ) => {
197191 return handleOsc7Command ( data , this . blockId , this . loaded ) ;
@@ -307,6 +301,60 @@ export class TermWrap {
307301 this . terminal . options . cursorBlink = cursorBlink ?? false ;
308302 }
309303
304+ setTermRenderer ( renderer : "webgl" | "canvas" ) {
305+ if ( renderer === "webgl" ) {
306+ if ( this . webglAddon != null ) {
307+ return ;
308+ }
309+ if ( ! WebGLSupported ) {
310+ renderer = "canvas" ;
311+ if ( this . canvasAddon != null ) {
312+ return ;
313+ }
314+ }
315+ } else {
316+ if ( this . canvasAddon != null ) {
317+ return ;
318+ }
319+ }
320+ if ( this . webglAddon != null ) {
321+ this . webglContextLossDisposable ?. dispose ( ) ;
322+ this . webglContextLossDisposable = null ;
323+ this . webglAddon . dispose ( ) ;
324+ this . webglAddon = null ;
325+ globalStore . set ( this . webglEnabledAtom , false ) ;
326+ }
327+ if ( this . canvasAddon != null ) {
328+ this . canvasAddon . dispose ( ) ;
329+ this . canvasAddon = null ;
330+ }
331+ if ( renderer === "webgl" ) {
332+ const addon = new WebglAddon ( ) ;
333+ this . webglContextLossDisposable = addon . onContextLoss ( ( ) => {
334+ this . setTermRenderer ( "canvas" ) ;
335+ } ) ;
336+ this . terminal . loadAddon ( addon ) ;
337+ this . webglAddon = addon ;
338+ globalStore . set ( this . webglEnabledAtom , true ) ;
339+ if ( ! loggedWebGL ) {
340+ console . log ( "loaded webgl!" ) ;
341+ loggedWebGL = true ;
342+ }
343+ } else {
344+ const addon = new CanvasAddon ( ) ;
345+ this . terminal . loadAddon ( addon ) ;
346+ this . canvasAddon = addon ;
347+ }
348+ }
349+
350+ getTermRenderer ( ) : "webgl" | "canvas" {
351+ return this . webglAddon != null ? "webgl" : "canvas" ;
352+ }
353+
354+ isWebGlEnabled ( ) : boolean {
355+ return this . webglAddon != null ;
356+ }
357+
310358 resetCompositionState ( ) {
311359 this . isComposing = false ;
312360 this . composingData = "" ;
@@ -422,6 +470,8 @@ export class TermWrap {
422470 } catch ( _ ) { }
423471 } ) ;
424472 this . promptMarkers = [ ] ;
473+ this . webglContextLossDisposable ?. dispose ( ) ;
474+ this . webglContextLossDisposable = null ;
425475 this . terminal . dispose ( ) ;
426476 this . toDispose . forEach ( ( d ) => {
427477 try {
0 commit comments