垃圾收集器破坏 WebGL 页面性能
Garbage Collector ruins WebGL page performance
作为一名 webGL 开发人员,这些图表让我心碎。不可能
如果垃圾收集器在主线程中运行超过半秒阻塞动画的工作流程,则流畅的动画可以不间断播放
我已经尝试了所有方法,缓存、对象池、声明全局变量并使我的返回函数像状态机一样工作,直到我最终发现即使是 empty call to RequestAnimationFrame itself 也会产生高达 1mb 的垃圾每秒
对 RAF 的乒乓调用也不会改变我系统上的垃圾创建率
假设当 GC 启动时不可能有一个完全响应的 webgl 页面,我想知道是否有任何替代我们通常在 webgl 项目中看到的通常代码结构。起初我想使用 webWorker 并在不中断动画渲染的情况下将主线程留给 GC,以深入研究 OffscreenCanvas 界面为代价,但看起来 it's only supported in Firefox atm
使用 setTimeout 应该仍然会产生抖动,并且理所当然地被认为是不好的做法,所以我想知道是否真的有避免 GC 中断的解决方法
看起来您的测试页面正在做一些本不应该做的事情。
- 解析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>
仍然没有看到任何阻碍我的帧率的东西
作为一名 webGL 开发人员,这些图表让我心碎。不可能 如果垃圾收集器在主线程中运行超过半秒阻塞动画的工作流程,则流畅的动画可以不间断播放
我已经尝试了所有方法,缓存、对象池、声明全局变量并使我的返回函数像状态机一样工作,直到我最终发现即使是 empty call to RequestAnimationFrame itself 也会产生高达 1mb 的垃圾每秒
对 RAF 的乒乓调用也不会改变我系统上的垃圾创建率
假设当 GC 启动时不可能有一个完全响应的 webgl 页面,我想知道是否有任何替代我们通常在 webgl 项目中看到的通常代码结构。起初我想使用 webWorker 并在不中断动画渲染的情况下将主线程留给 GC,以深入研究 OffscreenCanvas 界面为代价,但看起来 it's only supported in Firefox atm
使用 setTimeout 应该仍然会产生抖动,并且理所当然地被认为是不好的做法,所以我想知道是否真的有避免 GC 中断的解决方法
看起来您的测试页面正在做一些本不应该做的事情。
- 解析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>
仍然没有看到任何阻碍我的帧率的东西