【问题标题】:Garbage Collector ruins WebGL page performance垃圾收集器破坏 WebGL 页面性能
【发布时间】:2016-03-10 13:24:29
【问题描述】:

作为一名 webGL 开发人员,这些图表让我心碎。不可能 如果垃圾收集器在主线程中运行阻塞动画工作流超过半秒,则可以不间断地播放流畅的动画

我已经尝试了所有方法,缓存、对象池、声明全局变量以及让我的返回函数像状态机一样工作,直到我最终发现即使 empty call to RequestAnimationFrame itself 每秒也可以产生高达 1mb 的垃圾

p>

对 RAF 的乒乓调用也不会改变我系统上的垃圾创建率

假设当 GC 启动时不可能有一个完全响应的 webgl 页面,我想知道是否有任何替代我们通常在 webgl 项目中看到的通常代码结构的替代方案。起初我想使用 webWorker 并将主线程留给 GC,而不会中断动画渲染,但代价是深入研究 OffscreenCanvas 界面,但似乎it's only supported in Firefox atm

使用 setTimeout 仍然会产生抖动并且被认为是不好的做法,所以我想知道是否真的有避免 GC 中断的解决方法

【问题讨论】:

  • stackoverflow.com/questions/17382321/… 几乎在 三年前 提出了同样的问题。还是没有修好。不过,有一种解决方法可能对您有用:) 另外,您确定在没有打开 DevTools 的情况下运行时会发生这种情况吗?他们一直在制造的垃圾。
  • “阻塞超过半秒” - 我在该图中没有看到超过 500 毫秒的 GC 暂停。
  • 编辑了答案,第一张图只是一个简单的测试。哪个潞安?在这一点上我会尝试一切

标签: javascript performance garbage-collection webgl


【解决方案1】:

看起来您的测试页面正在做一些它不应该做的事情。

  • 解析 HTML

为什么它每帧都解析 HTML?仅当 HTML 更改为 someElement.innerHTML = "..." 时才会发生这种情况。不要那样做。

如果你要更新一个值,比如 FPS,试试这个

<div class="fps">FPS:<span id="fps"></span></div>

然后将 div 设为固定宽度,这样它就不会根据 fps 改变大小

.fps { width: 100px }

然后为fps值做一个文本节点

var fpsNode = document.createTextNode("");
var fpsSpan = document.getElementById("fps");
fpsSpan.appendChild(fpsNode);

现在你可以像这样更新 fps

fpsSpan.nodeValue = someFPSNumber;

那是我的巫术。我实际上并没有对其进行分析,但理论上这样做意味着不需要 HTML 解析,因为每一帧都没有新的 HTML。最重要的是,因为宽度是固定的,所以没有布局可做。

此外,您应该考虑将类似的东西放在它自己的堆栈上下文中

.fps { position: absolute; z-index: 2 }

这样,浏览器有望将该元素放入它自己的纹理中,这样它就不必与同一堆叠上下文中的其他元素一起重新渲染。

检查空白 rAF

function nothing() {
  requestAnimationFrame(nothing);
}
nothing();

我大约每 1.5 秒看到一次 1 毫秒的次要 GC。您每帧有 16 毫秒,因此为 GC 占用 1 毫秒不会毁了您的一天。

让我们在上面添加我的建议和一些 WebGL

"use strict";
var countNode = document.createTextNode("");
var countElem = document.getElementById("count");
countElem.appendChild(countNode);

twgl.setDefaults({attribPrefix: "a_"});
var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);

var tex = twgl.createTexture(gl, {
  min: gl.NEAREST,
  mag: gl.NEAREST,
  src: [
    255, 255, 255, 255,
    192, 192, 192, 255,
    192, 192, 192, 255,
    255, 255, 255, 255,
  ],
});

var camera = m4.identity();
var view = m4.identity();
var projection = m4.identity();
var viewProjection = m4.identity();
var world = m4.identity();
var worldInverseTranspose = m4.identity();
var worldViewProjection = m4.identity();
    
var uniforms = {
  u_lightWorldPos: new Float32Array([1, 8, -10]),
  u_lightColor: new Float32Array([1, 0.8, 0.8, 1]),
  u_ambient: new Float32Array([0, 0, 0, 1]),
  u_specular: new Float32Array([1, 1, 1, 1]),
  u_shininess: 50,
  u_specularFactor: 1,
  u_diffuse: tex,
  u_viewInverse: camera,
  u_world: world,
  u_worldInverseTranspose: worldInverseTranspose,
  u_worldViewProjection: worldViewProjection,
  
};
                             
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var count = 0;                            

function render(time) {
  ++count;
  countNode.nodeValue = count;

  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10, projection);

  m4.lookAt(eye, target, up, camera);
  m4.inverse(camera, view);
  m4.multiply(view, projection, viewProjection);
  m4.rotationY(time, world);

  m4.inverse(world, worldInverseTranspose);
  m4.transpose(worldInverseTranspose, worldInverseTranspose);
  
  m4.multiply(world, viewProjection, worldViewProjection);

  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
html, body, canvas {
  margin: 0;
  width: 100%;
  height: 100%;
}
.count {
  width: 100px;
  position: absolute;
  left: 1em;
  top: 1em;
  background-color: rgba(0,0,0,0.8);
  z-index: 2;
  padding: 1em;
  color: white;
}
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
   <script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;

attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;

varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

void main() {
  v_texCoord = a_texcoord;
  v_position = (u_worldViewProjection * a_position);
  v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
  v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
  v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
  gl_Position = v_position;
}
  </script>
  <script id="fs" type="notjs">
precision mediump float;

varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}

void main() {
  vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
  vec3 a_normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(a_normal, surfaceToLight),
                    dot(a_normal, halfVector), u_shininess);
  vec4 outColor = vec4((
  u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
                u_specular * litR.z * u_specularFactor)).rgb,
      diffuseColor.a);
  gl_FragColor = outColor;
}
</script>
<canvas id="c"></canvas>
<div class="count">
  count: <span id="count"></span>
</div>

仍然没有看到任何会影响我的帧率的东西

【讨论】:

  • 在全屏模式下,GC 仍然每 1.5 秒左右有一个小暂停。我希望有办法防止这种情况。也许 OffscreenCanvas 会有所作为(假设 GC 发生在另一个线程上)?
猜你喜欢
  • 2013-04-20
  • 2012-04-02
  • 1970-01-01
  • 2011-03-18
  • 1970-01-01
  • 2021-09-10
  • 2014-01-19
  • 1970-01-01
  • 2011-09-05
相关资源
最近更新 更多