使用JavaScript检测设备的CPU/GPU性能?

22

(这个问题并不特定于three.js,但我会以它作为例子)

最近我一直在使用three.js开发Web应用程序界面,并编写了一些漂亮的回退(针对桌面浏览器)在WebGL和Canvas渲染器之间切换。

但现在的问题是如何正确地检测设备性能,这个问题有两个方面:

  1. 浏览器功能(静态功能,如webgl/canvas):这在Web社区内已经得到了很好的解决,使用简单的功能检测即可。
  2. 设备性能:这是难点,在没有直接访问设备硬件信息的情况下,我们需要一些方法来判断是否应该回退到较低硬件需求的代码。

一个值得注意的例子: Firefox移动版/Opera移动版声称支持WebGL,但存在bug或受设备硬件限制。

到目前为止,我想到了一些解决方法:

  1. 使用常见功能作为性能指标 - 例如触摸设备通常具有较弱的硬件性能。缺点是不太具有未来性。
  2. 列出已知的有问题的浏览器/设备 - UA嗅探将是不可避免的,而且很难维护。
  3. 性能测试 - 因此提出这个问题,除了运行代码并测量帧速率之外,是否还有更好的方法?

或者也许这不必那么难,还有其他建议吗?

4个回答

20

在一个项目中,我们想要使用高性能桌面CPU/GPU上可用的canvas特性以及像平板电脑和手机这样的较低速度设备。为此,我们采用了一种性能测量方法。

基本上,我们从最简单的场景复杂度开始,如果渲染循环时间小于33毫秒,就增加复杂度(如果稍后渲染循环开始花费太长时间,我们也会减少复杂度)。

我想,在你的情况下,你可能需要运行快速的canvas和WebGL性能测试,然后选择其中之一。经过一段时间的研究,我没有发现更好的解决方法。


1
喜欢你找到的解决方案。感谢分享。 - jlmakes
2
+1,动态检测和调整是唯一的出路。特别是如果您想支持任何使用电池并可能在飞行中切换到低性能模式以节省电池的设备。如果您只进行静态检测一次,您将错过环境中的这种变化。 - Mikko Rantalainen

1
这是我用three.js编写用于基准测试webgl的片段代码:
import {
  Mesh,
  MeshStandardMaterial,
  PerspectiveCamera,
  Scene,
  SphereGeometry,
  WebGLRenderer,
} from "three";

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export enum GPUPerformanceLevel {
  HIGH = "HIGH",
  LOW = "LOW",
}

function approxRollingAverage(
  average: number,
  value: number,
  history = 50
) {
  average -= average / history;
  average += value / history;

  return average;
}

/**
 * Three.js based webgl benchmark
 *
 * In summary, the `run` method adds `meshPerStep` new spheres every step (frame)
 * and measures the fps. If we're able to perform >=`thresholds.steps` of these
 * steps, without the fps dropping below `thresholds.fps`, then we label the device
 * `GPUPerformanceLevel.HIGH`.
 */
export class GPUBenchmark {
  scene = new Scene();
  material = new MeshStandardMaterial();
  geometry = new SphereGeometry();

  static thresholds = { fps: 10, steps: 50 };
  static meshPerFrame = 1000;
  static framesPerStep = 5;

  async run(debug = false): Promise<GPUPerformanceLevel> {
    const camera = new PerspectiveCamera(75);
    const renderer = new WebGLRenderer();

    let tPrev = performance.now() / 1000;
    let steps = 0;
    let meshCnt = 0;
    let fps = 30;

    let passedThreshold = false;

    const animate = async () => {
      const time = performance.now() / 1000;
      const fpsMeasured = Math.min(1 / (time - tPrev), 120);
      tPrev = time;

      fps = approxRollingAverage(fps, fpsMeasured, 5);

      if (debug) {
        console.log(`fps: ${fps} fpsMeasured: ${fpsMeasured} steps: ${steps} meshCnt: ${meshCnt}`);
      }

      if (
        fps > GPUBenchmark.thresholds.fps &&
        steps < GPUBenchmark.thresholds.steps
      ) {
        requestAnimationFrame(animate);

        if (steps++ % GPUBenchmark.framesPerStep == 0) {
          meshCnt += this.step();
        }
        renderer.render(this.scene, camera);
      } else {
        passedThreshold = true;
      }
    };

    animate();

    while (!passedThreshold) {
      await sleep(1);
    }

    this.cleanup();
    renderer.dispose();
    const level = GPUBenchmark.stepsToPerfLevel(steps);

    if (debug) {
      console.log("device benchmarked at level:", level);
    }

    return level;
  }

  private step(): number {
    const meshPerStep = GPUBenchmark.meshPerFrame * GPUBenchmark.framesPerStep;
    for (let i = 0; i < meshPerStep; i++) {
      const sphere = new Mesh(this.geometry, this.material);
      sphere.frustumCulled = false;

      this.scene.add(sphere);
    }

    return meshPerStep;
  }

  private cleanup() {
    for (const obj of this.scene.children) {
      this.scene.remove(obj);
    }

    //@ts-ignore
    this.scene = null;

    this.material.dispose();
    this.geometry.dispose();

    //@ts-ignore
    this.material = null;
    //@ts-ignore
    this.geometry = null;
  }

  private static stepsToPerfLevel(numSteps: number): GPUPerformanceLevel {
    if (numSteps >= GPUBenchmark.thresholds.steps) {
      return GPUPerformanceLevel.HIGH;
    } else {
      return GPUPerformanceLevel.LOW;
    }
  }
}

1

1
虽然这些统计数据很有趣,但我不知道如何将其应用于我的网站的个别用户(以实现适当的回退,而无需设备/浏览器黑名单)。此外,从代码中看来,检测是在一个 iframe 中完成的,我对这种方法的选择持怀疑态度。 - bitinn
1
确实。这是非常有趣的信息,但它只是特性检测,你可以为每个用户自己完成。除非你想在特性实现和性能之间建立一些相当可疑的关联,否则它不会告诉你性能。 - desau
1
不再了。网站已经死了。 - metalim

0
如果您正在使用three.js,您可以使用detector.js来查找webgl是否已启用。另外,避免使用canvasrenderer会有所帮助。使用webglrenderer或softwarerender,因为它们允许更多的顶点。软件渲染器是相当新的,需要一些工作,但可以使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接