簡體   English   中英

最大限度地利用 WebGL2 而不使其過載

[英]Maximize WebGL2 usage without overloading it

我的 Web 應用程序進行了很長時間的計算,然后顯示結果。 我正在使用 WebGL2 進行計算 - 繪制到屏幕外的 2D 紋理中。 我不能簡單地在單個 WegGL 調用中完成它 - 計算將花費太長時間並導致“丟失上下文”錯誤。 所以我將計算分成矩形部分,每個部分都可以在短時間內繪制。

問題在於調度這些 WebGL 調用。 如果我經常這樣做,瀏覽器可能會變得無響應或帶走我的 WebGL 上下文。 如果我不經常這樣做,計算將花費比必要時間更長的時間。 我知道偶爾丟失上下文是正常的,我害怕系統地丟失它,因為我使用 GPU 太多了。

我能想到的最好的辦法是有一些工作與睡眠的比率,並在我用於計算的一小部分時間內睡眠。 我想我可以使用 WebGL2 同步對象來等待發出的調用完成並粗略估計它們花費了多少時間。 像這樣:

var workSleepRatio = 0.5; // some value
var waitPeriod = 5;
var sync;
var startTime;

function makeSomeWebglCalls() {
    startTime = performance.now();
    sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
    for (<estimate how many rectangles we can do so as not to waste too much time on waiting>) {
        gl.drawArrays(); // draw next small rectangle
    }
    setTimeout(timerCb, waitPeriod);
}

function timerCb() {
    var status = gl.getSyncParameter(sync, gl.SYNC_STATUS);
    if (status != gl.SIGNALED) {
        setTimeout(timerCb, waitPeriod);
    } else {
        gl.deleteSync(sync);
        
        var workTime = performance.now() - startTime;
        setTimeout(makeSomeWebglCalls, Math.min(1000, workTime * workSleepRatio));
    }
}

makeSomeWebglCalls();

這種方法不是很好,它有以下問題:

  • 不知道將 workSleepRatio 設置為什么。
  • 在 GPU 工作完成和我的計時器回調之間浪費了時間。 不能依賴 gl.clientWaitSync 因為它的超時參數在許多瀏覽器中被限制為零,即使在 Web Worker 線程中也是如此。
  • 無論我將 workSleepRatio 設置得多大,我仍然無法確定瀏覽器不會認為我做得太多並帶走了 WebGL 上下文。 也許 requestAnimationFrame 可以在被節流時以某種方式用於減慢速度,但是用戶在等待計算完成時無法切換選項卡。
  • setTimeout 可能會受到瀏覽器的限制,並且比請求的睡眠時間更長。

所以,簡而言之,我有以下問題:

  • 如何使用 WebGL 既不超載又不浪費時間? 這甚至可能嗎?
  • 如果不可能,那么有沒有更好的方法來處理這個問題?

您可以使用EXT_disjoint_timer_query_webgl2嗎?

 function main() { const gl = document.createElement('canvas').getContext('webgl2', { powerPreference: 'high-performance', }); log(`powerPreference: ${gl.getContextAttributes().powerPreference}\\n\\n`); if (!gl) { log('need WebGL2'); return; } const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2'); if (!ext) { log('need EXT_disjoint_timer_query_webgl2'); return; } const vs = `#version 300 es in vec4 position; void main() { gl_Position = position; } `; const fs = `#version 300 es precision highp float; uniform sampler2D tex; out vec4 fragColor; void main() { const int across = 100; const int up = 100; vec2 size = vec2(textureSize(tex, 0)); vec4 sum = vec4(0); for (int y = 0; y < up; ++y) { for (int x = 0; x < across; ++x) { vec2 start = gl_FragCoord.xy + vec2(x, y); vec2 uv = (mod(start, size) + 0.5) / size; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; uv = texture(tex, uv).xy; sum += texture(tex, uv); } } fragColor = sum / float(across * up); } `; const programInfo = twgl.createProgramInfo(gl, [vs, fs]); const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl); const pixels = new Uint8Array(1024 * 1024 * 4); for (let i = 0; i < pixels.length; ++i) { pixels[i] = Math.random() * 256; } // creates a 1024x1024 RGBA texture. const tex = twgl.createTexture(gl, {src: pixels}); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); const waitFrame = _ => new Promise(resolve => requestAnimationFrame(resolve)); const widthHeightFromIndex = i => { const height = 2 ** (i / 2 | 0); const width = height * (i % 2 + 1); return { width, height }; }; async function getSizeThatRunsUnderLimit(gl, limitMs) { log('size time in milliseconds'); log('--------------------------------'); for (let i = 0; i < 32; ++i) { const {width, height} = widthHeightFromIndex(i); const timeElapsedMs = await getTimeMsForSize(gl, width, height); const dims = `${width}x${height}`; log(`${dims.padEnd(11)} ${timeElapsedMs.toFixed(1).padStart(6)}`); if (timeElapsedMs > limitMs) { return widthHeightFromIndex(i - 1); } } } (async () => { const limit = 1000 / 20; const {width, height} = await getSizeThatRunsUnderLimit(gl, limit); log('--------------------------------'); log(`use ${width}x${height}`); })(); async function getTimeMsForSize(gl, width, height) { gl.canvas.width = width; gl.canvas.height = height; gl.viewport(0, 0, width, height); // prime the GPU/driver // this is voodoo but if I don't do this // all the numbers come out bad. Even with // this the first test seems to fail with // a large number intermittently gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); for (;;) { const query = gl.createQuery(); gl.beginQuery(ext.TIME_ELAPSED_EXT, query); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); gl.endQuery(ext.TIME_ELAPSED_EXT); gl.flush(); for (;;) { await waitFrame(); const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE); if (available) { break; } } const disjoint = gl.getParameter(ext.GPU_DISJOINT_EXT); if (!disjoint) { const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT); gl.deleteQuery(query); return timeElapsed / (10 ** 6); // return milliseconds } gl.deleteQuery(query); } } } main(); function log(...args) { const elem = document.createElement('pre'); elem.textContent = args.join(' '); document.body.appendChild(elem); }
 pre { margin: 0; }
 <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

在我的 2014 Macbook Pro Dual GPU(Intel/Nvidia)上,首先,即使我要求高性能 Chrome 給我低功耗,這意味着它使用的是 Intel 集成 GPU。

1x1 像素上的第一個計時通常是約 17 毫秒間歇性的,通常但並非總是如此。 我不知道如何解決這個問題。 我可以保持計時,直到 1x1 像素是一些更合理的數字,比如時間 5 次,直到它 < 1 毫秒,如果永遠不會失敗?

powerPreference: low-power

size        time in milliseconds
--------------------------------
1x1           16.1
2x1            0.0
2x2            0.0
4x2            0.0
4x4            0.0
8x4            0.1
8x8            0.1
16x8           0.0
16x16          0.0
32x16          0.0
32x32          0.0
64x32         13.6
64x64         35.7
128x64        62.6
--------------------------------
use 64x64

在配備英特爾集成 GPU 的 2018 年末 Macbook Air 上進行的測試顯示了類似的問題,除了第一個時間在 42 毫秒時更糟。

size        time in milliseconds
--------------------------------
1x1           42.4
2x1            0.0
2x2            0.0
4x2            0.0
4x4            0.0
8x4            0.0
8x8            0.0
16x8           0.0
16x16          0.0
32x16          0.0
32x32          0.0
64x32          0.0
64x64         51.5
--------------------------------
use 64x32

此外,時間安排有點虛假。 注意我的 2014 MBP,32x32 是 0ms,64x32 突然變成 13ms。 我希望 32x32 為 6.5 毫秒。 上面的MBA也是一樣,一切都是0然后突然51ms!??!??

在帶有 Nvidia RTX 2070 的 Windows 10 桌面上運行它似乎更合理。 1x1 時序是正確的,時序按預期增長。

powerPreference: low-power

size        time in milliseconds
--------------------------------
1x1            0.0
2x1            0.0
2x2            0.0
4x2            0.0
4x4            0.0
8x4            0.0
8x8            0.0
16x8           0.0
16x16          0.0
32x16          0.1
32x32          0.1
64x32          2.4
64x64          2.9
128x64         3.1
128x128        6.0
256x128       15.4
256x256       27.8
512x256       58.6
--------------------------------
use 256x256

此外,在所有系統上,如果我沒有在計時失敗之前預先繪制每個尺寸並且所有計時都出現 > 16 毫秒。 添加預繪圖似乎有效,但它是巫術。 我什至嘗試預繪制 1x1 像素而不是寬度乘高度像素作為預繪制,但失敗了!?!?!?

此外,Firefox 不支持 EXT_disjoint_timer_query_webgl2 我相信這是因為精確計時可以從其他進程竊取信息。 Chrome 通過站點隔離修復了這個問題,但我猜 Firefox 還沒有做到這一點。

注意:WebGL1 具有用於類似功能的EXT_disjoint_timer_query

更新:英特爾 GPU 上的問題可能與模糊時間以避免安全問題有關? Intel GPU 使用統一內存(意味着它們與 CPU 共享內存)。 我不知道。 chrome 安全文章提到降低具有統一內存的設備的精度。

我想即使沒有時間擴展,您也可以通過檢查 requestAnimationFrame 時間來嘗試查看是否可以在 60hz 以下渲染。 不幸的是,我的經驗也是它可能是片狀的。 任何事情都可能導致 rAF 超過 60fps。 也許用戶正在運行其他應用程序。 也許他們在 30hz 的顯示器上。 等等...也許在一定數量的幀上平均時間或取多個時間的最低讀數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM