最大限度地提高WebGL2的使用率,而又不会使其过载

如何解决最大限度地提高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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-