You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

requestAnimationFrame帧率超显示器刷新率,闪烁异常求助

问题:WebGL中requestAnimationFrame帧率超出显示器刷新率导致画面闪烁

问题详情

  • 使用requestAnimationFrame逐帧修改像素颜色(从黑转白),渲染依赖上一帧的纹理内容
  • 实际帧率超出显示器239.96Hz刷新率,通过window.performance.now()计算得到200、333.33、250等波动数值
  • 画面闪烁不稳定,原以为requestAnimationFrame会与显示器帧率同步,怀疑与垂直同步(v-sync)机制失效有关

设备配置

  • 系统:Pop!_OS 22.04 LTS
  • 显卡:Ellesmere [Radeon RX 470/480/570/570X/580/580X/590]
  • 显示器:DisplayPort-0连接,已开启TearFree模式

相关JS代码片段

//...
const gl = canvas.getContext(
            'webgl2', 
            {
                desynchronized: false, //trying to force vsync?
            }
        ); 
///...
        function render() {
            nid = requestAnimationFrame(render);

            let n_ts_ms_now = window.performance.now();
            let n_ms_delta = n_ts_ms_now - n_ts_ms;

            // console.log(n_ms_delta)
            console.log(`n_fps: ${1000/n_ms_delta})`);
            n_ts_ms = n_ts_ms_now;
            n += 1;

            // if(n_ms_delta > n_ms_max){
                const nextTextureIndex = 1 - currentTextureIndex;

                gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[nextTextureIndex]);
                gl.viewport(0, 0, canvas.width, canvas.height);

                gl.useProgram(program);

                gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
                gl.enableVertexAttribArray(positionAttributeLocation);
                gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, textures[currentTextureIndex]);

                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                // Swap textures
                currentTextureIndex = nextTextureIndex;

                // Render to the canvas
                gl.bindFramebuffer(gl.FRAMEBUFFER, null);
                gl.viewport(0, 0, canvas.width, canvas.height);
                gl.bindTexture(gl.TEXTURE_2D, textures[currentTextureIndex]);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
                console.log(n)

            // }
        }

已尝试操作

  • 手动通过xrandr开启TearFree
  • 使用vblank_mode=1参数启动Chrome和Firefox
  • 手动限制帧率时,画面闪烁恢复稳定,但希望找到更适配的原生同步方案

解决方案建议

  1. 修正渲染流程与WebGL上下文配置

    • desynchronized: false的设置方向正确,但你当前在同一帧内完成了离屏帧缓冲渲染和画布渲染两次提交操作,可能干扰浏览器的v-sync机制。建议将最终画布渲染逻辑合并,确保每一帧只有一次屏幕提交操作。
    • 部分Linux浏览器对WebGL离屏渲染的v-sync支持存在差异,尝试去掉手动的二次画布渲染,直接将帧缓冲结果交换到屏幕。
  2. 使用requestAnimationFrame原生时间戳

    • 放弃用window.performance.now()计算时间差,改用requestAnimationFrame传入的高精度时间戳——它是浏览器同步到刷新周期的基准,计算时间差更准确:
      let lastTimestamp = 0;
      function render(timestamp) {
          nid = requestAnimationFrame(render);
          const n_ms_delta = timestamp - lastTimestamp;
          lastTimestamp = timestamp;
          console.log(`n_fps: ${1000/n_ms_delta})`);
          // ... 剩余渲染逻辑
      }
      
  3. 系统层面强制全局垂直同步

    • 仅靠浏览器启动参数可能不足,在Pop!_OS中:
      • 打开显示设置,找到AMD显卡专属配置选项,强制开启全局垂直同步
      • 或通过驱动配置文件(如/etc/modprobe.d/amdgpu.conf)添加options amdgpu vblank_mode=1,重启系统后生效
  4. 优化手动帧率限制逻辑

    • 既然手动限制能稳定画面,基于显示器239.96Hz刷新率计算目标间隔,精准跳过冗余帧:
      const TARGET_FPS = 239.96;
      const FRAME_INTERVAL = 1000 / TARGET_FPS;
      let lastRenderTime = 0;
      
      function render(timestamp) {
          nid = requestAnimationFrame(render);
          if (timestamp - lastRenderTime < FRAME_INTERVAL) return;
          lastRenderTime = timestamp;
      
          // ... 你的渲染逻辑
      }
      
    • 该方式会让渲染严格对齐显示器刷新周期,避免帧率波动导致的闪烁

内容的提问来源于stack exchange,提问作者Jonas Frey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:55:05