Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGL context loss handling #5

Closed

Conversation

juancampa
Copy link

This PR adds a helper class in WebglRenderer that manages webgl contexts. This is needed because:

  • Contexts can be lost at any time if the driver decides that it needs the resources for something else.
  • At least one browser (chrome) has a limit to the number of simultaneous contexts that can exist at any given time.

When a context is lost:

  • if the terminal is paused: don't do anything, it'll be recreated when unpausing
  • if the terminal is unpaused: try to kill a paused terminal to free up resources and recover the context immediately
    • if there are not enough paused terminals, it means we've reached the max number of webgl contexts allowed by the browser, so print a warning and give up.

When a renderer is unpaused, if its context has been lost, we simply get rid of the whole renderer and ask Terminal to create a new one.

Renderers are kept ordered by least-recently-used so that less used ones are killed first when resources are needed.

If one of the context that are being recovered was just lost, it means that we're currently at a resource limit so we need to kill a few paused renderers. if we fail to release other contexts (i.e. everything is unpaused), there's no point in trying to recover anything because we would be killing an unpaused renderers. In this case, just give up.


I used a class expression to have access to WebglRenderer private fields while keeping this whole thing encapsulated

@juancampa
Copy link
Author

juancampa commented Jan 21, 2019

Testing cases

Here's a few tests I ran on Hyper. Not sure if there's currently a way to test this on the current demo page.

  • Create 16+ splits in one tab (there's no endless cycle of create/destroy contexts)
  • Create 16+ tabs
  • Create <16 splits and then reach the limit by creating tabs
  • Contexts are lost in LRU order
  • Contexts are not lost unless enough terminals exist simultaneously (Chrome limit)
  • Go crazy creating and destroying tabs and splits, making sure there's never a blank terminal unless we have 16+ unpaused terminals

@juancampa
Copy link
Author

juancampa commented Jan 21, 2019

There's another approach to context-loss handling where the browser determines when to recreate contexts. I don't think this would work for us since we need control over the context recreation (to have LRU priority, for example)

this._canvas.height = 0;

if (!this._gl.isContextLost()) {
console.log('Force context loss');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen automatically after dispose on the next GC?

Copy link
Author

@juancampa juancampa Jan 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, however, we force it so that another context can be created before the next GC. If we don't force it here, Chrome might not notice there's an available context and could kill an active one.

What I observed is:

  1. open 20 tabs (the first 4 will lose their context)
  2. close the last 16 tabs
  3. close one more tab
  4. result: Chrome will warn that the max number of contexts has been reached
  5. expected: no warning

So, it seems like one of these is happening:

  1. GC is not firing during the closing of the 17 tabs
  2. GC isn't collecting the underlying webgl contexts (maybe it's lazy by design?)
  3. Someone's keeping a ref to this instance, preventing it from being GC

I'll grab a snapshot to make sure (3) is not happening

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm all webgl contexts are eventually GC'd. But we still need this to prevent Chrome from killing the oldest context (as opposed to the LRU one)

// This is unique to WebglRenderer because 'webgl2' contexts can become
// invalidated at any moment, typically by creating other canvases with
// 'webgl2' context, but also arbitrarily by the OS.
private static _contextHelper = new class {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pull this into WebglContextHelper.ts?

}

public loseContext(): void {
this._gl.getExtension('WEBGL_lose_context').loseContext()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, that seems outdated. It's definitely supported on Chrome and Chromium: https://codesandbox.io/embed/xpjjjmn1oq?view=preview

@Tyriar
Copy link
Owner

Tyriar commented Mar 8, 2019

Need the refactor to pull WebglContextHelper into its own class before merging this, this will involve breaking WebglContextHelper's dependence on private members of WebglRenderer. Also there are some lint issues that need to be fixed too.

@Tyriar
Copy link
Owner

Tyriar commented Jun 23, 2019

Closing off in favor of xtermjs#2253

@Tyriar Tyriar closed this Jun 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants