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

Too many active WebGL contexts #2233

Closed
andrevenancio opened this issue Dec 11, 2015 · 29 comments
Closed

Too many active WebGL contexts #2233

andrevenancio opened this issue Dec 11, 2015 · 29 comments

Comments

@andrevenancio
Copy link
Contributor

I'm building an app integrated with React which loads several individual experiments, in pixi.js, three.js etc. Because I can't use an iframe to load the experiments, I need to clean after myself.

Every time there is a navigation to a new experiment, I'm checking if the a renderer exists, or not, and if it does I'm destroying it).

dummy code

if renderer
    renderer.destroy(true);
    renderer = null

renderer = new PIXI.autoDetectRenderer ......

It seems that I'm correctly cleaning my webgl renderer, however after 16 pages get loaded, I get the warning saying WARNING: Too many active WebGL contexts. Oldest context will be lost. This shouldnt be a problem on desktop as we only have a warning, but on the ipad it crashes the browser and a message appears: X A problem occurred with this web page, so it was reloaded..

I was looking at threejs and how they implement a solution for this, and they have the following code

Am I doing anything silly?

@samuraiseoul
Copy link

@andrevenancio
I'm no expert in Pixi seeing as I've only been using it since monday or tuesday, however. If you are having multiple renderers and canvases due to many experiments(like this) in an idea of only one needs to be accessed at one time, I would have a global renderer or another, and keep the experiments in an array. Then when you need to display it, just pass the stage from each experiment into the renderer.

var renderer = PIXI.autoDetectRenderer({blah});
var experiments = [];
experiments.push(experiment1);
experiments.push(experiment2);
experiments.push(experiment3);

var currentExperiment = $('#experminetSelector').val();
renderer.render(experiments[currentExperiment]);

This way you're only making the renderer once and just passing it the stage from each experiment. Though this only works if you're trying to only render one page and have all the experiments on it rather than having a lot of different experiment pages.

DISCLAIMER: I'm new to pixi, and didn't try this out, your mileage may vary. Also it still sounds as if you found some kind of bug that maybe the devs would want to look into or give some guidance to.

@englercj
Copy link
Member

Having a single renderer and multiple scenes should be fine. 1 renderer === 1 canvas element.

@andrevenancio
Copy link
Contributor Author

Hey guys, thanks for your feedback. @samuraiseoul What you're suggesting isnt silly at all, and that's how I would normally proceed to have several experiments in one framework... however, I will need to make another solution as the experiments can de done using pixi.js threejs or plain webgl. Also, on top of that, because of the type of the transitions already defined in the react structure, at a point you will see the old experiment, and the new experiment at the same time, rendering simultaneously, so the idea of one renderer won't work....

Plus.... this is a work around for the problem, not a solution itself... If the .destroy() method actually doesn't destroy the reference to the webgl context... what's the point? :(

@GoodBoyDigital
Copy link
Member

Yo buddy! Totally agree that the destroy function is not exactly doing what its supposed to if the contexts are not being destroyed :)

I wander, are you using autodetectRenderer ? It could be that the test context we create to detect if the browser is webGL capable is not being destroyed.

@englercj
Copy link
Member

Some extra info: You can't destroy a webgl context, they are garbage collected (unless something has changed?)

I think we remove the references we hold, and optionally remove the canvas element (which is required for context cleanup) but then it is up to the browser to do the clean. It is possible that contexts are leaking, or that they are being created faster than the browser wants. Honestly I'm not sure.

Do dev tools indicate leaks around contexts like this? Maybe that can help? Sounds like something is keeping the context around.. Maybe a js ref to the context or the a js ref to the underlying canvas?

@andrevenancio
Copy link
Contributor Author

This is why I still love iframes no matter what.

@GoodBoyDigital I'm using the autodetectRenderer like this

@renderer = new PIXI.autoDetectRenderer canvas.width, canvas.height, { view: canvas, antialias: false, backgroundColor: 0xffffff }

I'm parking this for now, will let you guys know what the solution is when I found one.. I was just thinking that forcing that context lost, would maybe force the garbage collection to clean it.. but not sure of it either :)

@davearel
Copy link
Contributor

I'm having the same issue. What I am building requires that Pixi be destroyed and rebuilt quite often and I'm concerned about the memory implications. Each WebGL instance is quite small at the moment, but it will get much much larger as we continue to build this out.

Any progress?

@davearel
Copy link
Contributor

To be clear, I'm using Phaser, but it seems to be calling Pixi's renderer.destroy method.

@andrevenancio
Copy link
Contributor Author

no luck yet. If you can either use one global renderer and you just render different scenes into it, or, just load content through an iframe and that disposes properly of whatever is hanging.

@GoodBoyDigital
Copy link
Member

Hey peeps, yes this is a bit of a tricky one really. As far as I can see we do everything we can to destroy a context. So all textures / programs arraybuffers ect are destroyed. What we could do is maybe pool the contexts once they are destroyed so that we reuse them rather than create a new one each time?

@andrevenancio
Copy link
Contributor Author

hey @GoodBoyDigital in my case i had an animation in/out so i would always needed 2 contexts... that's the problem with integrating with React and not making it full pixi... anyway, not sure this is a bug you can fix per say.. I think its more like the browser manages the contexts... don't even know if this hack from three.js works... maybe give it a go on ur V4 and try to open 16+ tabs with pixi https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js#L289

Otherwise no worries mate, I already changed the architecture of my app... iframe FTW :)

@GoodBoyDigital
Copy link
Member

Good stuff man! I will definitely look into adding the three.js trick too.

@davearel
Copy link
Contributor

I would love to use one global rendered. In fact, that's my ultimate goal. However, I'd need a way to stream code into the existing context.

In other words, the user is writing the code. It's not pre-written.

@vicapow
Copy link

vicapow commented Feb 9, 2016

I think I found a good solution. This allows me to have map overlay React components that don't have to know about each other and allow's React to own the DOM.

It uses this technique to force the context loss:

gl.getExtension('WEBGL_lose_context').loseContext();

Here's a full example:

var document = require('global/document');
var PIXI = require('pixi.js');

// I want to keep this canvas / renderer around. For example, this might be a bottom layer PIXI / React map overlay.
var canvas1 = document.createElement('canvas');
document.body.appendChild(canvas1);
createRenderer(0xff0000, canvas1);

function createRenderer(color, canvas) {
  canvas = canvas || document.createElement('canvas');
  var renderer = new PIXI.WebGLRenderer(800, 600, {view: canvas});
  var stage = new PIXI.Container();
  var graphics = new PIXI.Graphics();
  graphics.beginFill(color, 0.5);
  graphics.drawCircle(0, 0, 200);
  graphics.endFill();
  stage.addChild(graphics);
  renderer.render(stage);
  return {renderer: renderer, stage: stage, graphics: graphics};
}

// Simulate frequent adding / removing of lots of PIXI / React map overlays on top.
for (var i = 0; i < 16; i++) {
  var canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  var scene = createRenderer(0x00ff00, canvas);
  // Uncomment to see that the original canvas isn't removed.
  /* scene.renderer.currentRenderTarget.gl
      .getExtension('WEBGL_lose_context').loseContext(); */
  scene.renderer.destroy();
  scene.stage.removeChild(scene.graphics);
  document.body.removeChild(canvas);
}

Maybe PIXI should add this to its own "destroy" method?

@englercj
Copy link
Member

englercj commented Feb 9, 2016

That makes sense if we own the context, which I think right now we assume we do (v3).

@bldrvnlw
Copy link

bldrvnlw commented Mar 4, 2016

In the function isWebGLSupported() (source/core/utils/index.js) replacing
return !!(gl && gl.getContextAttributes().stencil);

with
var success = !!(gl && gl.getContextAttributes().stencil); gl.getExtension('WEBGL_lose_context').loseContext(); gl = undefined; return success;

fixed the problem for me. It was most prominent in Google Chrome - Firefox worked either way. As I'm a bit of a WebGL noob I'd be really grateful for comment on this approach.

@englercj
Copy link
Member

englercj commented Mar 4, 2016

Good call losing the context forcibly when checking, that is a great idea since we know (100%) that we own that context.

@weepy
Copy link
Contributor

weepy commented Mar 17, 2016

Did this get merged ? We are having the same problem.

@ylambda
Copy link
Contributor

ylambda commented Apr 11, 2016

ping @englercj i think this can be closed now

@monfera
Copy link

monfera commented Jan 11, 2017

gl.getExtension('WEBGL_lose_context').loseContext() may suppress the warning but the spec says it merely simulates losing the context, until WEBGL_lose_context.restoreContext() is called - which can restore the context only if it wasn't truly lost in the first place.

@bldrvnlw
Copy link

This might be useful: lose_context() is only a simulation (according to the spec) but in this thread (Public WebGL: WEBGL_lose_context we learn (from mozilla developer Jeff Gilbert) that in Firefox loseContext() is a byword for "release this context and its
resources"
.

The problem is that other browsers handle this differently. There are comments from a Google developer (Ken Russell) recommending : Please use the explicit delete* APIs on the WebGLRenderingContext to release GPU resources that your application is no longer using.

I have to admit I haven't experimented with any of this but it looks like any solution is going to be browser specific.

@monfera
Copy link

monfera commented Jan 27, 2017

Interesting FF implementation note. Indeed, Ken's advice about deleting individual resources looks like the standard way to go, FF is but one among the many popular browsers and even FF behavior might change without notice. I also haven't tested per browser because of not wanting to rely on implementation detail and fakeable user agent string or browser sniffing and additional code paths when there's an alternative.

@distinctdan
Copy link
Contributor

I was doing some testing for my app today on iOS and ran into this error. Is there a known workaround? My pixi view is a subcomponent of a screen on my app, so it gets destroyed when the user navigates away from that page. I was thinking, would it work to somehow detach/reattach the same renderer to the view's canvas to work around the issue?

@englercj
Copy link
Member

englercj commented Dec 20, 2018

The workaround is to not create multiple contexts. Just reuse ones you have. If you navigate pages, then it shouldn't matter what was created on the previous page.

@distinctdan
Copy link
Contributor

distinctdan commented Dec 20, 2018

Hmm, so how would I go about reusing a context? I'm using angularJS, so I'm working within a templating framework. Like, could I detach the canvas element from the DOM and store it, then when the user comes back to the page, reattach it to its place in the page? It'd be great if there was a way within PIXI to say "use this existing renderer + context and attach them to this new canvas", but I don't understand how it happens under the hood.

The reason I care is I'm using one big pixi view as the background of my app, and I have a smaller one that I use on my settings page. If the user goes to the settings page multiple times, it kills the main background one because it was created first.

Seems like this would be an issue with using PIXI within any single page app.

@bigtimebuddy
Copy link
Member

Saving the instance of your PIXI.Application/PIXI.WebGLRenderer/PIXI.CanvasRenderer will also save the instance of your canvas element. For instance, in PIXI.Application, you can get the view property which is the HTMLCanvasElement. You can insert this canvas element and remove it from your AngularJS/React app. In PIXI you can tell the Renderer which canvas element to use by passing it as an option, however, you shouldn't use this. Instead, allow PIXI to create it's own canvas element internally and just appendChild to some DOM container.

<div id="pixi-container"></div>
// maintain the reference to this Application even as your SPA view gets destroyed
const app = new PIXI.Application();

// Insert in the DOM or whatever the equivalent is in Angular
document.querySelector("#pixi-container").appendChild(app.view);

@distinctdan
Copy link
Contributor

Ok, thanks, I'll give that a try. Doesn't look bad at all actually.

@distinctdan
Copy link
Contributor

Alright, detaching and reappending the view instead of destroying it solved my issue, thanks for the help guys.

@lock
Copy link

lock bot commented Dec 20, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked and limited conversation to collaborators Dec 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests