context:我正在使用名为 openglobus 的映射库来渲染 3d 行星。 Openglobus 有一个图块层,您可以在其中提供一个生成图块的函数,该函数可以返回画布。所以我制作的是一个函数,它生成这些画布以渲染为地球仪上的地图图层。为了保持高性能,该函数是异步的,并调用 Webworker 来执行实际的图块生成。因此画布是由图块生成器函数在主线程中制作的。然后画布将其控制权转移到屏幕外。接下来,网络工作者将获取所有必需的参数。当网络工作人员完成时,画布将应用于地球仪。
Tile 生成器函数如下所示(WM 代表 Worker Manager,它将请求分配给多个 Worker):
export default function BaseLayer(WM) {
return new layer.CanvasTiles(`cnv`, {
isBaseLayer: true,
drawTile: function (material, applyCanvas) {
const { tileZoom, tileX, tileY } = material.segment;
const extent = material.segment.getExtentLonLat();
const res = 700;
const cnv = document.createElement("canvas");
cnv.width = res;
cnv.height = res;
const offscreen = cnv.transferControlToOffscreen();
WM.makeBaseTile({
extent,
tileX,
tileY,
tileZoom,
res,
offscreen,
}).then(_ => applyCanvas(cnv));
},
});
}
Workers 在页面加载时创建并且不会被销毁。它们被重复用于经常使用的每个图块和缓存数据。绘制这些图块的工作代码的结构如下(简化):
self.onmessage = async (e) => {
const { id, extent, tileX, tileY, tileZoom, res, offscreen } = e.data;
const cnv = offscreen
const ctx = cnv.getContext("2d");
// draw fallback background color
ctx.fillStyle = g.biomeColors[13]; //"#85a478";
ctx.fillRect(0, 0, res, res);
// Fetching data from online resources to draw
let [biomeRes, waterRes, hillshade] = await Promise.all([...axios get requests]);
// before water is added, draw the color of the biome (background)
biomeRes.data.forEach((BIOME) => {
BIOME.forEach((b) => {
// custom function to draw geometries onto canvasses
drawShape(ctx, b, color, "#000000", e.data, false, 0, false);
});
});
// if water is an empty array, there is no water in this tile
if (waterRes.length) {
// jiggle is a custom function to add noise to geometries
const water = jiggle(waterRes, extent, tileZoom, 3, 2);
water.forEach((p) =>
drawShape(ctx, p, "#5b99a6", "#2d4d54", e.data, false, g.outline)
);
}
}
// toggle tile borders on/off
if (true) {
ctx.beginPath();
ctx.rect(0, 0, cnv.width, cnv.height);
ctx.lineWidth = 3;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.closePath();
ctx.font = "50px serif";
ctx.fillStyle = "black";
ctx.fillText(`${tileZoom}`, 10, 60);
}
// draw hillshade, but blend by multiply to avoid white background
// make opacity fade out when it reaches zoom level of 15, start at 10
const img = await createImageBitmap(await hillshade.blob());
ctx.globalCompositeOperation = "multiply";
ctx.globalAlpha = Math.max(0, 1 - (tileZoom - 10) / 5);
ctx.drawImage(img, 0, 0, res, res);
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1.0;
// ctx.commit();
postMessage({ origin: JSON.stringify([id, tileX, tileY]) });
问题:当我以这种方式编码时,我遇到了一些图块未加载的问题。有些人这样做,但有些人则不这样做。它发生在 Chrome 和 Firefox 上,所以我认为它与这篇文章或这个现有的 Chrome 错误(该错误也已标记为已修复)无关。
这个问题似乎在生成时间较长的复杂图块上最为普遍。但这都是异步的,所以我不明白为什么这可能是原因。
不过我确实找到了解决办法。这就是为什么我认为这不是 openglobus 库的问题,而是屏幕外画布的工作方式的问题。通过在工作进程结束时在 OffscreenCanvasRenderingContext2D 上使用 commit() 函数,我得到了一个完美的工作示例:
但是此提交功能仅适用于 Safari 和 Firefox 浏览器,不适用于基于 Chrome(ium) 的浏览器。有谁能更好地理解为什么在没有提交功能的情况下会发生这种情况,或者如何在 Chrome 中修复此问题?我已经找了一段时间了,但似乎没有相当于提交功能的东西。
控制
<canvas>
已发送给工作人员的占位符OffscreenCanvas
的内容仅在工作人员的“更新渲染”步骤中更新。您需要等待“更新渲染”发生才能访问占位符上的内容<canvas>
。
从你的问题来看,它是如何使用的,或者 Promise 等待什么,有点不清楚,但基本上有两种方法可以规避这个问题:
继续使用占位符
<canvas>
并等待下一个绘画帧,例如在您的 Worker 中,您将在 requestAnimationFrame()
回调中执行绘图,然后通过 postMessage()
通知您的主线程,这应该在更新完成后在下一个任务中处理。
请勿使用占位符
<canvas>
,而是使用 OffscreenCanvas
方法将
ImageBitmap
内容传输到 transferToImageBitmap()
对象,该方法将强制同步渲染到 ImageBitmap
中。最好的方法是直接使用返回的 ImageBitmap
提供库的代码,但如果它确实需要使用 <canvas>
元素,那么您可以从该元素 (ImageBitmapRenderingContext
) 创建一个
getContext("bitmaprenderer")
并提供给它ImageBitmap
(ctx.transferFromImageBitmap(bmp)
)。