mapbox-gl 中是否有每页地图限制?
Is there any maps per page limitation in mapbox-gl?
我正在尝试使用 mapbox-gl 在同一页面上放置 17 张小地图并面向:
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,并在 Mac OS chrome 版本 66.0.3359.181(官方构建)(64 位)中对其进行测试塞拉利昂 10.12.6 (16G1036)
我猜你运气不好。浏览器限制 WebGL 实例的数量。 There are workarounds but to use them would probably require changes to the way mapbox-gl is implemented. I suggest you ask them 如果他们考虑实施其中一种变通方法(假设他们尚未实施)。
我想到了另一种可能性,那就是在 JavaScript 中进行您自己的 WebGL 虚拟化。这可能不是一个好的解决方案,因为它不会跨地图共享资源,而且它可能太重了。
在我的脑海中,你必须创建一个屏幕外 canvas 并覆盖 HTMLCanvasElement.prototype.getContext
这样当有人制作 webgl
上下文时你 return虚拟语境。您将包装每个函数,如果该虚拟上下文与上次使用的虚拟上下文不匹配,您将保存所有 webgl 状态并为新上下文恢复状态。您还必须保持帧缓冲区以匹配每个 canvas 的绘图缓冲区,当当前帧缓冲区绑定为 null
时绑定它们,如果 canvas 大小发生变化则调整它们的大小,然后渲染到当前事件退出时,屏幕外 canvas 然后 canvas2d.drawImage
到它们各自的 canvases。这是最重的最后一部分。
在半伪代码中
// 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);
}
}
您还需要捕获导致渲染的事件,这对于每个应用程序都是唯一的。如果应用程序使用 requetsAnimationFrame 进行渲染,那么可能类似于
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();
});
我正在尝试使用 mapbox-gl 在同一页面上放置 17 张小地图并面向:
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,并在 Mac OS chrome 版本 66.0.3359.181(官方构建)(64 位)中对其进行测试塞拉利昂 10.12.6 (16G1036)
我猜你运气不好。浏览器限制 WebGL 实例的数量。 There are workarounds but to use them would probably require changes to the way mapbox-gl is implemented. I suggest you ask them 如果他们考虑实施其中一种变通方法(假设他们尚未实施)。
我想到了另一种可能性,那就是在 JavaScript 中进行您自己的 WebGL 虚拟化。这可能不是一个好的解决方案,因为它不会跨地图共享资源,而且它可能太重了。
在我的脑海中,你必须创建一个屏幕外 canvas 并覆盖 HTMLCanvasElement.prototype.getContext
这样当有人制作 webgl
上下文时你 return虚拟语境。您将包装每个函数,如果该虚拟上下文与上次使用的虚拟上下文不匹配,您将保存所有 webgl 状态并为新上下文恢复状态。您还必须保持帧缓冲区以匹配每个 canvas 的绘图缓冲区,当当前帧缓冲区绑定为 null
时绑定它们,如果 canvas 大小发生变化则调整它们的大小,然后渲染到当前事件退出时,屏幕外 canvas 然后 canvas2d.drawImage
到它们各自的 canvases。这是最重的最后一部分。
在半伪代码中
// 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);
}
}
您还需要捕获导致渲染的事件,这对于每个应用程序都是唯一的。如果应用程序使用 requetsAnimationFrame 进行渲染,那么可能类似于
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();
});