Есть ли какие-либо ограничения на количество страниц в mapbox-gl? - PullRequest
0 голосов
/ 04 июня 2018

Я пытаюсь разместить 17 маленьких карт на одной странице, используя mapbox-gl и обращаясь к ним:

WARNING: Too many active WebGL contexts. Oldest context will be lost.

Uncaught TypeError: Failed to execute 'shaderSource' on 'WebGLRenderingContext': parameter 1 is not of type 'WebGLShader'.
    at new Program (mapbox-gl.js:182)
    at Painter._createProgramCached (mapbox-gl.js:178)
    at Painter.useProgram (mapbox-gl.js:178)
    at setFillProgram (mapbox-gl.js:154)
    at drawFillTile (mapbox-gl.js:154)
    at drawFillTiles (mapbox-gl.js:154)
    at Object.drawFill [as fill] (mapbox-gl.js:154)
    at Painter.renderLayer (mapbox-gl.js:178)
    at Painter.render (mapbox-gl.js:178)
    at e._render (mapbox-gl.js:497)

У меня возникла та же проблема, когда я пыталсяу меня на одной странице много галерей видов улиц Google, но так как мой вид улицы не должен быть виден в одно и то же время, я прекратил использовать один и тот же вид улицы, меняя адрес динамически.

Но для списка карт необходимо показать, что много картпользователю.Не могу показать их по одному.Не уверен, как я мог решить эту проблему.

Я использую mapbox-gl@0.45.0 и тестирую его в chrome версии 66.0.3359.181 (официальная сборка) (64-разрядная версия) на Mac OS Sierra10.12.6 (16G1036)

1 Ответ

0 голосов
/ 05 июня 2018

Полагаю, вам не повезло.Браузеры ограничивают количество экземпляров WebGL. Есть обходные пути , но для их использования, вероятно, потребуются изменения в способе реализации mapbox-gl.Я предлагаю спросить у них , рассмотрят ли они вопрос об использовании одного из обходных путей, предполагая, что они этого еще не сделали.

Есть еще одна возможность, которая приходит на ум, это ваша собственная виртуализация WebGL в JavaScript.Это, вероятно, не очень хорошее решение, потому что оно не будет распределять ресурсы между картами и может быть слишком тяжелым.

Вдобавок ко всему, вам придется создать закадровый холст и переопределить HTMLCanvasElement.prototype.getContext, поэтомучто когда кто-то создает webgl контекст, вы возвращаете виртуальный контекст.Вы бы обернули каждую функцию, и если этот виртуальный контекст не соответствует последнему используемому виртуальному контексту, вы сохраните все состояние webgl и восстановите состояние для нового контекста.Вам также нужно будет сохранить кадровые буферы в соответствии с буфером рисования для каждого холста, связать их, когда текущая привязка кадрового буфера равна null, и изменить их размер, если размер холста изменился, и затем отобразить их за пределами экрана, а затем canvas2d.drawImage на ихсоответствующие холсты каждый раз, когда выходит текущее событие.Это последняя часть, которая будет самой тяжелой.

В полупсевдокоде

// This is just off the top of my head and is just pseudo code
// but hopefully gives an idea of how to virtualize WebGL.

const canvasToVirtualContextMap = new Map();
let currentVirtualContext = null;
let sharedWebGLContext;
const baseState = makeDefaultState();

HTMLCanvasElement.prototype.getContext = (function(origFn) {

  return function(type, contextAttributes) {
    if (type === 'webgl') {
      return createOrGetVirtualWebGLContext(this, type, contextAttributes);
    }
    return origFn.call(this, contextAttributes);
  };

}(HTMLCanvasElement.prototype.getContext));

class VirutalWebGLContext {
  constructor(cavnas, contextAttributes) {
    this.canvas = canvas;
    // based on context attributes and canvas.width, canvas.height 
    // create a texture and framebuffer
    this._drawingbufferTexture = ...;
    this._drawingbufferFramebuffer = ...;
    
    // remember all WebGL state (default bindings, default texture units,
    // default attributes and/or vertex shade object, default program,
    // default blend, stencil, zbuffer, culling, viewport etc... state
    this._state = makeDefaultState();
  }
}

function makeDefaultState() {
  const state ={};
  state[WebGLRenderingContext.ARRAY_BUFFER] = null;
  ... tons more ...
}

// copy all WebGL constants and functions to the prototype of
// VirtualWebGLContext

for (let key in WebGLRenderingContext.protoype) {
  const value = WebGLRenderingContext.prototype[key];
  let newValue = value;
  switch (key) {
    case 'bindFramebuffer': 
      newValue = virutalBindFramebuffer;
      break;
    case 'clear':
    case 'drawArrays':
    case 'drawElements':
      newValue = createDrawWrapper(value);
      break;
    default:
      if (typeof value === 'function') {
        newValue = createWrapper(value); 
      }
      break;
   }
   VirtualWebGLContext.prototype[key] = newValue;
}

function virutalBindFramebuffer(bindpoint, framebuffer) {
  if (bindpoint === WebGLRenderingContext.FRAMEBUFFER) {
    if (target === null) {
      // bind our drawingBuffer
      sharedWebGLContext.bindFramebuffer(bindpoint, this._drawingbufferFramebuffer);
    }
  }

  sharedWebGLContext.bindFramebuffer(bindpoint, framebuffer);
}  

function createWrapper(origFn) {
  // lots of optimization could happen here depending on specific functions
  return function(...args) {
    makeCurrentContext(this);
    resizeCanvasIfChanged(this);
    return origFn.call(sharedWebGLContext, ...args);
  };
}

function createDrawWrapper(origFn) {
  const newFn = createWrapper(origFn);
  return function(...args) {
    // a rendering function was called so we need to copy are drawingBuffer
    // to the canvas for this context after the current event.
    this._needComposite = true;
    return newFn.call(this, ...args);
  };
}

function makeCurrentContext(vctx) {
  if (currentVirtualContext === vctx) {
    return;
  }
  
  // save all current WebGL state on the previous current virtual context
  saveAllState(currentVirutalContext._state);
  
  // restore all state for the 
  restoreAllState(vctx._state);
  
  // check if the current state is supposed to be rendering to the canvas.
  // if so bind vctx._drawingbuffer
  
  currentVirtualContext = vctx;
}

function resizeCanvasIfChanged(vctx) {
  if (canvas.width !== vtx._width || canvas.height !== vctx._height) {
    // resize this._drawingBuffer to match the new canvas size
  }  
}

function createOrGetVirtualWebGLContext(canvas, type, contextAttributes) {
  // check if this canvas already has a context
  const existingVirtualCtx = canvasToVirtualContextMap.get(canvas);
  if (existingVirtualCtx) {
    return existingVirtualCtx;
  }
  
  if (!sharedWebGLContext) {
    sharedWebGLContext = document.createElement("canvas").getContext("webgl");
  }
  
  const newVirtualCtx = new VirtualWebGLContext(canvas, contextAttributes);
  canvasToVirtualContextMap.set(canvas, newVirtualCtx);
  
  return newVirtualCtx;   
}

function saveAllState(state) {
  // save all WebGL state (current bindings, current texture units,
  // current attributes and/or vertex shade object, current program,
  // current blend, stencil, zbuffer, culling, viewport etc... state
  state[WebGLRenderingContext.ARRAY_BUFFER] = sharedGLState.getParameter(gl.ARRAY_BUFFER_BINDING);
  state[WebGLRenderingContext.TEXTURE_2D] = sharedGLState.getParameter(gl.TEXTURE_BINDING_2D);
  ... tons more ...
}

function restoreAllState(state) {
  // resture all WebGL state (current bindings, current texture units,
  // current attributes and/or vertex shade object, current program,
  // current blend, stencil, zbuffer, culling, viewport etc... state
  gl.bindArray(gl.ARRAY_BUFFER, state[WebGLRenderingContext.ARRAY_BUFFER]);
  gl.bindTexture(gl.TEXTURE_2D, state[WebGLRenderingContext.TEXTURE_2D]);
  ... tons more ...
}

function renderAllDirtyVirtualCanvas() {
  let setup = false;
  for (const vctx of canvasToVirtualContextMap.values()) {
    if (!vctx._needComposite) {
      continue;
    }
    
    vctx._needComposite = false;
     
    if (!setup) {
      setup = true;
      // save all current WebGL state on the previous current virtual context
      saveAllState(currentVirutalContext._state);
      currentVirutalContext = null;
      
      // set the state back to the default
      restoreAllState(sharedGlContext, baseState);
        
      // setup whatever state we need to render vctx._drawinbufferTexture
      // to the canvas.
      sharedWebGLContext.useProgram(programToRenderCanvas);
      ...
    }
    
    // draw the drawingbuffer's texture to the canvas
    sharedWebGLContext.bindTexture(gl.TEXTURE_2D, vctx._drawingbufferTexture);
    sharedWebGLContext.drawArrays(gl.TRIANGLES, 0, 6);
  }
}

вам также потребуется перехватывать события, которые вызывают рендеринг, который будет уникальным для каждого приложения.Если приложение использует для визуализации requireetsAnimationFrame, то может быть что-то вроде

window.requestAnimationFrame = (function(origFn) {

  return function(callback) {
    return origFn.call(window, (time) {
      const result = callback(time);
      renderAllDirtyVirtualCanvases();
      return result;
    };
  };

}(window.requestAnimationFrame));

Если приложение рендерится на другие события, например, скажем mousemove, то, возможно, что-то вроде этого

let someContextNeedsRendering;

function createDrawWrapper(origFn) {
  const newFn = createWrapper(origFn);
  return function(...args) {
    // a rendering function was called so we need to copy are drawingBuffer
    // to the canvas for this context after the current event.
    this._needComposite = true;

    if (!someContextsNeedRendering) {
      someContextsNeedRendering = true;
      setTimeout(dealWithDirtyContexts, 0);
    }

    return newFn.call(this, ...args);
  };
}

function dealWithDirtyContexts() {
  someContextsNeedRendering = false;
  renderAllDirtyVirtualCanvas();
});

Заставляет меня задуматься если кто-то уже сделал это .

...