如何解决最大限度地提高WebGL2的使用率,而又不会使其过载
我的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线程中,其timeout参数也受零限制。
- 无论我将workSleepRatio设置得有多大,我仍然不能确定浏览器不会认为我做得太多,也不会占用WebGL上下文。也许在请求节流时可以使用requestAnimationFrame来减慢它的速度,但是在等待计算完成时,用户无法切换选项卡。
- setTimeout可能会被浏览器限制,并且睡眠时间会长于请求的时间。
因此,简而言之,我有以下问题:
- 如何在不使WebGL过载但又不浪费时间的情况下利用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;
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,height) {
gl.canvas.width = width;
gl.canvas.height = height;
gl.viewport(0,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,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(英特尔/英伟达)上,虽然我要求高性能Chrome,但我却获得了低功耗,这意味着它正在使用英特尔集成GPU。
在1x1像素上的第一次计时通常是间歇性的〜17ms,通常但并非总是如此。我不知道该如何解决。我可以一直计时,直到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
在2018年末装有Intel Integrated GPU的Macbook Air上进行的测试显示了一个类似的问题,除了第一次计时的时间更差42ms。
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.5ms。与上面的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
另外,在所有系统上,如果我没有在时序失败之前绘制每个尺寸,并且所有时序> 16ms。添加预绘图似乎可行,但这是伏都教。我什至尝试以1x1像素而不是宽度乘高度像素进行预绘制,但失败了!?!!?!?
此外,Firefox不支持EXT_disjoint_timer_query_webgl2,我相信这是因为精确计时使从其他进程中窃取信息成为可能。 Chrome使用site isolation修复了此问题,但我猜想Firefox尚未做到这一点。
注意:WebGL1具有EXT_disjoint_timer_query
,具有类似功能。
更新:英特尔GPU上的问题可能与模糊计时以避免安全问题有关?英特尔GPU使用统一内存(这意味着它们与CPU共享内存)。我不知道。 chrome security article提到降低具有统一内存的设备的精度。
我想即使没有定时扩展,您也可以通过检查requestAnimationFrame定时来尝试查看是否可以在60hz以下进行渲染。不幸的是,根据我的经验,它也可能是片状的。任何情况都可能导致rAF的速度超过60fps。也许用户正在运行其他应用程序。也许他们在30hz的显示器上。等等...可能是在一定数量的帧中平均时序,或者在多个时序中获取最低的读数。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。