Pixi.js: Muitos contextos WebGL ativos

Criado em 11 dez. 2015  ·  29Comentários  ·  Fonte: pixijs/pixi.js

Estou construindo um aplicativo integrado ao React que carrega vários experimentos individuais, em pixi.js, three.js etc. Como não posso usar um iframe para carregar os experimentos, preciso limpar depois de mim mesmo.

Cada vez que há uma navegação para um novo experimento, estou verificando se existe um renderizador, ou não, e se existe estou destruindo-o).

código fictício

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

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

Parece que estou limpando corretamente meu renderizador webgl, no entanto, após 16 páginas serem carregadas, recebo o aviso dizendo WARNING: Too many active WebGL contexts. Oldest context will be lost. Isso não deve ser um problema no desktop, pois só temos um aviso, mas no ipad ele trava o navegador e uma mensagem aparece: X A problem occurred with this web page, so it was reloaded. .

Eu estava olhando para threejs e como eles implementam uma solução para isso, e eles têm o seguinte código

Estou fazendo algo bobo?

Comentários muito úteis

Acho que encontrei uma boa solução. Isso me permite ter componentes React de sobreposição de mapa que não precisam se conhecer e permitir que o React seja o proprietário do DOM.

Ele usa esta técnica para forçar a perda de contexto:

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

Aqui está um exemplo completo:

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);
}

Talvez o PIXI deva adicionar isso ao seu próprio método de "destruição"?

Todos 29 comentários

@andrevenancio
Não sou especialista em Pixi, visto que só o uso desde segunda ou terça-feira, no entanto. Se você tiver vários renderizadores e telas devido a muitos experimentos ( como este ) em uma ideia de que apenas um precisa ser acessado por vez, eu teria um renderizador global ou outro e manteria os experimentos em um array. Então, quando você precisar exibi-lo, basta passar o estágio de cada experimento para o renderizador.

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

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

Desta forma, você só está criando o renderizador uma vez e apenas passando-o ao estágio de cada experimento. Embora isso só funcione se você estiver tentando renderizar apenas uma página e tiver todos os experimentos nela, em vez de ter muitas páginas de experimentos diferentes.

AVISO DE RESPONSABILIDADE: Sou novo no pixi e não tentei fazer isso, sua milhagem pode variar. Além disso, ainda soa como se você tivesse encontrado algum tipo de bug que talvez os desenvolvedores gostariam de examinar ou dar alguma orientação.

Ter um único renderizador e várias cenas deve ser bom. 1 renderizador === 1 elemento de tela.

Olá pessoal, obrigado pelo seu feedback. @samuraiseoul O que você está sugerindo não é bobo, e é assim que eu normalmente faria para ter vários experimentos em um framework ... entretanto, vou precisar fazer outra solução, pois os experimentos podem ser feitos usando pixi.js threejs ou simples webgl. Além disso, além disso, por causa do tipo de transições já definidas na estrutura de reação, em um ponto você verá o experimento antigo e o novo experimento ao mesmo tempo, renderizando simultaneamente, de modo que a ideia de um renderizador venceu não funciona ....

Além disso ... esta é uma solução alternativa para o problema, não uma solução em si ... Se o método .destroy () realmente não destrói a referência ao contexto webgl ... qual é o ponto? :(

Ei amigo! Concordo totalmente que a função de destruição não está fazendo exatamente o que deveria se os contextos não estivessem sendo destruídos :)

Eu divago, você está usando autodetectRenderer ? Pode ser que o contexto de teste que criamos para detectar se o navegador é compatível com webGL não esteja sendo destruído.

Algumas informações extras: você não pode destruir um contexto webgl, eles são coletados como lixo (a menos que algo tenha mudado?)

Acho que removemos as referências que possuímos e, opcionalmente, removemos o elemento canvas (que é necessário para a limpeza do contexto), mas então cabe ao navegador fazer a limpeza. É possível que os contextos estejam vazando ou que estejam sendo criados mais rápido do que o navegador deseja. Honestamente, não tenho certeza.

As ferramentas de desenvolvimento indicam vazamentos em contextos como este? Talvez isso possa ajudar? Parece que algo está mantendo o contexto ao redor. Talvez a js ref para o contexto ou a js ref para a tela subjacente?

É por isso que ainda adoro iframes, aconteça o que acontecer.

@GoodBoyDigital Estou usando autodetectRenderer assim

<strong i="9">@renderer</strong> = new PIXI.autoDetectRenderer canvas.width, canvas.height, { view: canvas, antialias: false, backgroundColor: 0xffffff }

Por enquanto, vou deixar vocês saberem qual é a solução quando eu encontrar um .. Eu estava pensando que forçar a perda desse contexto talvez forçaria a coleta de lixo a limpá-lo .. mas também não tenho certeza disso. :)

Estou tendo o mesmo problema. O que estou construindo requer que Pixi seja destruído e reconstruído com bastante frequência e estou preocupado com as implicações de memória. Cada instância do WebGL é muito pequena no momento, mas ficará muito maior à medida que continuarmos a desenvolvê-la.

Algum progresso?

Para ser claro, estou usando o Phaser, mas parece estar chamando o método renderer.destroy do Pixi.

sem sorte ainda. Se você pode usar um renderizador global e apenas renderizar cenas diferentes nele, ou apenas carregar o conteúdo por meio de um iframe e isso descarta adequadamente o que estiver pendurado.

Ei peeps, sim, este é um pouco complicado, na verdade. Pelo que posso ver, fazemos tudo o que podemos para destruir um contexto. Assim, todas as texturas / programas, arraybuffers, etc. são destruídas. O que poderíamos fazer é agrupar os contextos depois de destruídos para que possamos reutilizá-los em vez de criar um novo a cada vez?

Ei @GoodBoyDigital no meu caso eu tinha uma animação dentro / fora, então eu sempre precisaria de 2 contextos ... esse é o problema de integração com React e não torná-lo pixel completo ... de qualquer maneira, não tenho certeza se esse é um bug que você pode consertar por dizer .. Eu acho que é mais parecido com o navegador gerencia os contextos ... nem sei se esse hack do three.js funciona ... talvez dê uma chance em seu V4 e tente abrir mais de 16 abas com pixi https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js#L289

Do contrário, não se preocupe, cara, eu já mudei a arquitetura do meu aplicativo ... iframe FTW :)

Boas coisas, cara! Definitivamente irei adicionar o truque do three.js também.

Eu adoraria usar um renderizado global. Na verdade, esse é meu objetivo final. No entanto, eu precisaria de uma maneira de transmitir o código para o contexto existente.

Em outras palavras, o usuário está escrevendo o código. Não está pré-escrito.

Acho que encontrei uma boa solução. Isso me permite ter componentes React de sobreposição de mapa que não precisam se conhecer e permitir que o React seja o proprietário do DOM.

Ele usa esta técnica para forçar a perda de contexto:

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

Aqui está um exemplo completo:

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);
}

Talvez o PIXI deva adicionar isso ao seu próprio método de "destruição"?

Isso faz sentido se formos donos do contexto, o que acho que agora assumimos que somos (v3).

Na função isWebGLSupported () (source / core / utils / index.js) substituindo
return !!(gl && gl.getContextAttributes().stencil);

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

resolveu o problema para mim. Era mais proeminente no Google Chrome - o Firefox funcionava de qualquer maneira. Como sou um pouco novato em WebGL, ficaria muito grato por comentar sobre essa abordagem.

É uma boa ideia perder o contexto forçosamente ao verificar, o que é uma ótima ideia, pois sabemos (100%) que o contexto é nosso.

Isso foi mesclado? Nós temos o mesmo problema.

ping @englercj acho que isso pode ser fechado agora

gl.getExtension('WEBGL_lose_context').loseContext() pode suprimir o aviso, mas a especificação diz que ele meramente simula a perda do contexto, até que WEBGL_lose_context.restoreContext() seja chamado - o que pode restaurar o contexto apenas se ele não tiver sido realmente perdido.

Isso pode ser útil: lost_context () é apenas uma simulação (de acordo com as especificações), mas neste tópico ( WebGL pública: WEBGL_lose_context , aprendemos (do desenvolvedor do mozilla Jeff Gilbert) que no Firefox loseContext() é sinônimo de " libere este contexto e seurecursos " .

O problema é que outros navegadores lidam com isso de forma diferente. Há comentários de um desenvolvedor do Google (Ken Russell) recomendando: Use as APIs delete * explícitas no WebGLRenderingContext para liberar recursos de GPU que seu aplicativo não está mais usando.

Tenho que admitir que não experimentei nada disso, mas parece que qualquer solução será específica do navegador.

Nota de implementação FF interessante. Na verdade, o conselho de Ken sobre a exclusão de recursos individuais parece ser o caminho padrão a seguir, o FF é apenas um entre os muitos navegadores populares e até mesmo o comportamento do FF pode mudar sem aviso prévio. Eu também não testei por navegador porque não quero contar com detalhes de implementação e string de agente de usuário falsificável ou detecção de navegador e caminhos de código adicionais quando há uma alternativa.

Eu estava fazendo alguns testes para meu aplicativo hoje no iOS e encontrei este erro. Existe uma solução alternativa conhecida? Minha visualização de pixi é um subcomponente de uma tela em meu aplicativo, portanto, é destruída quando o usuário sai dessa página. Eu estava pensando, funcionaria de alguma forma desanexar / reconectar o mesmo renderizador à tela da visualização para contornar o problema?

A solução alternativa é não criar vários contextos. Basta reutilizar os que você possui. Se você navegar nas páginas, não importa o que foi criado na página anterior.

Hmm, então como eu faria para reutilizar um contexto? Estou usando o angularJS, então estou trabalhando em uma estrutura de modelos. Por exemplo, eu poderia desanexar o elemento canvas do DOM e armazená-lo e, em seguida, quando o usuário voltar para a página, reconectá-lo ao seu lugar na página? Seria ótimo se houvesse uma maneira no PIXI de dizer "use este renderizador + contexto existente e anexe-os a esta nova tela", mas não entendo como isso acontece nos bastidores.

O motivo pelo qual me importo é que estou usando uma grande visualização em pixi como plano de fundo do meu aplicativo e tenho uma menor que uso na página de configurações. Se o usuário acessar a página de configurações várias vezes, ele elimina a página de fundo principal porque foi criada primeiro.

Parece que isso seria um problema ao usar o PIXI em qualquer aplicativo de página única.

Salvar a instância de seu PIXI.Application / PIXI.WebGLRenderer / PIXI.CanvasRenderer também salvará a instância de seu elemento de tela. Por exemplo, em PIXI.Application, você pode obter a propriedade view que é o HTMLCanvasElement. Você pode inserir este elemento de tela e removê-lo de seu aplicativo AngularJS / React. No PIXI você pode dizer ao Renderer qual elemento de tela usar passando-o como uma opção, entretanto, você não deve usar isso. Em vez disso, permita que o PIXI crie seu próprio elemento canvas internamente e apenas anexeChild a algum contêiner DOM.

<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);

Ok, obrigado, vou tentar. Não parece nada mal, na verdade.

Tudo bem, desanexar e reaplicar a visualização em vez de destruí-la resolveu meu problema, obrigado pela ajuda pessoal.

Este tópico foi bloqueado automaticamente, pois não houve nenhuma atividade recente depois que ele foi fechado. Abra um novo problema para bugs relacionados.

Esta página foi útil?
0 / 5 - 0 avaliações