Performance drop after updating from r106 to r112

Hi,

I tried the below instancing example in chrome, firefox, opera and on my iphone 10. I think chrome has the lowest fps compared to others. My computer spec is ASUS 16gb ram - Intel HD Graphics 530.

http://raw.githack.com/donmccurdy/three.js/examples-instancing-performance/examples/webgl_instancing_performance.html

Interesting thing is, iphone 10 gives the best results.

This is chrome below worse then opera and firefox

I also got a significant fps drop in my project with r112. When i get close to the objects (instanced meshes or meshes), fps drops from 60 to 35-40. I used r106 for a long time, yes fps needs to drop when you get too close to objects but it wasnt dropping this much.

Far from object


Close to object

Close to instanced mesh

Looking for solutions to fix this issue. Thanks.

It’s hard to reproduce what you’re comparing here… your link uses THREE.InstancedMesh, which wasn’t added until r109. Is there an example you can share with both an r106 and r112 version?

Any given example may have different performance in different browsers. three.js (and browsers) are usually open to ways to improve that, but those differences do not necessarily indicate three.js bugs. A regression from one version of three.js to another is likely to be worth investigating though.

3 Likes

just a couple of things that come to mind:

  • materials. When you’re looking close up at something, material of that thing fills most of your screen. Possibly that material’s fragment shader got a whole lot more expensive in the new three.js for intel’s integrated GPU.
  • Draw order. It’s possible that under the new library version order of the objects is different, resulting in fewer depth rejections and much higher overdraw.

Still, 60FPS for something that looks like a proper game - you’re doing pretty awesome already :slight_smile:

3 Likes

You are right, i was in panic so i think i asked two different questions. I was using my own shader code before and now using instanced mesh. But realised that it has nothing to do with instanced mesh, and related with general performance.

Thanks for your support! Yes i believe related with fragment shader of the material. I will change MeshStandard materials to MeshPhong materials to gain some more fps and will continue to check some other more ways.

In https://github.com/mrdoob/three.js/issues/18265 someone else has reported that MeshStandardMaterial is a bit more expensive in r112 than r111, because of some of the IBL improvements I think. Using cheaper materials for less important objects (like scenery) may help. I don’t know if Phong is much cheaper than Standard (curious what your results are?) but Lambert should be substantially cheaper than either.

2 Likes

I tried material comparison with the tank object. It is around 650 kb.

In the below example tank’s material is MeshStandardMaterial and uses a diffuse, normalmap, lightmap and 77kb metalnessmap;

And now with the MeshPhongMaterial and uses a diffuse, normalmap and a lightmap;

It seems MeshPhongMaterial gives a better result when you get close to object.
I tried with other objects too. MeshPhongMaterial doesnt drop any fps when you get too close.

Thanks for the tip. All the objects i use have lightmaps and normalmaps so using MeshLambert material will not help me. I tried a comparison above between MeshStandard and MeshPhong and resolved the issue by using MeshPhong instead of MeshStandad for now.
This worked for objects but didnt work for instanced meshes. Instanced mesh performance is still low so i decided to use MeshLambert material for the instanced rocks.

Well, this could happen because more fragments have to be rendered with a relatively expensive material. I can also notice this effect in the official glTF example of three.js. I have initially 60 FPS but why but when I move the camera closer so the helmet covers the entire screen, the scene is rendered with 45 FPS.

3 Likes

This example is not working on my computer and throw an error like this below

THREE.WebGLShader: gl.getShaderInfoLog() fragment
1: #extension GL_OES_standard_derivatives : enable
2: #extension GL_EXT_shader_texture_lod : enable
3: precision highp float;
4: precision highp int;
5: #define HIGH_PRECISION
6: #define SHADER_NAME BackgroundCubeMaterial
7: #define GAMMA_FACTOR 2
8: #define USE_ENVMAP
9: #define ENVMAP_TYPE_CUBE_UV
10: #define ENVMAP_MODE_REFLECTION
11: #define ENVMAP_BLENDING_NONE
12: #define FLIP_SIDED
13: #define TEXTURE_LOD_EXT
14: uniform mat4 viewMatrix;
15: uniform vec3 cameraPosition;
16: uniform bool isOrthographic;
17: #define TONE_MAPPING
18: #ifndef saturate
19: #define saturate(a) clamp( a, 0.0, 1.0 )
20: #endif
21: uniform float toneMappingExposure;
22: uniform float toneMappingWhitePoint;
23: vec3 LinearToneMapping( vec3 color ) {
24: 	return toneMappingExposure * color;
25: }
26: vec3 ReinhardToneMapping( vec3 color ) {
27: 	color *= toneMappingExposure;
28: 	return saturate( color / ( vec3( 1.0 ) + color ) );
29: }
30: #define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
31: vec3 Uncharted2ToneMapping( vec3 color ) {
32: 	color *= toneMappingExposure;
33: 	return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );
34: }
35: vec3 OptimizedCineonToneMapping( vec3 color ) {
36: 	color *= toneMappingExposure;
37: 	color = max( vec3( 0.0 ), color - 0.004 );
38: 	return pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );
39: }
40: vec3 ACESFilmicToneMapping( vec3 color ) {
41: 	color *= toneMappingExposure;
42: 	return saturate( ( color * ( 2.51 * color + 0.03 ) ) / ( color * ( 2.43 * color + 0.59 ) + 0.14 ) );
43: }
44: vec3 toneMapping( vec3 color ) { return ACESFilmicToneMapping( color ); }
45: 
46: vec4 LinearToLinear( in vec4 value ) {
47: 	return value;
48: }
49: vec4 GammaToLinear( in vec4 value, in float gammaFactor ) {
50: 	return vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );
51: }
52: vec4 LinearToGamma( in vec4 value, in float gammaFactor ) {
53: 	return vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );
54: }
55: vec4 sRGBToLinear( in vec4 value ) {
56: 	return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );
57: }
58: vec4 LinearTosRGB( in vec4 value ) {
59: 	return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
60: }
61: vec4 RGBEToLinear( in vec4 value ) {
62: 	return vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );
63: }
64: vec4 LinearToRGBE( in vec4 value ) {
65: 	float maxComponent = max( max( value.r, value.g ), value.b );
66: 	float fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );
67: 	return vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );
68: }
69: vec4 RGBMToLinear( in vec4 value, in float maxRange ) {
70: 	return vec4( value.rgb * value.a * maxRange, 1.0 );
71: }
72: vec4 LinearToRGBM( in vec4 value, in float maxRange ) {
73: 	float maxRGB = max( value.r, max( value.g, value.b ) );
74: 	float M = clamp( maxRGB / maxRange, 0.0, 1.0 );
75: 	M = ceil( M * 255.0 ) / 255.0;
76: 	return vec4( value.rgb / ( M * maxRange ), M );
77: }
78: vec4 RGBDToLinear( in vec4 value, in float maxRange ) {
79: 	return vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );
80: }
81: vec4 LinearToRGBD( in vec4 value, in float maxRange ) {
82: 	float maxRGB = max( value.r, max( value.g, value.b ) );
83: 	float D = max( maxRange / maxRGB, 1.0 );
84: 	D = min( floor( D ) / 255.0, 1.0 );
85: 	return vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );
86: }
87: const mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );
88: vec4 LinearToLogLuv( in vec4 value )  {
89: 	vec3 Xp_Y_XYZp = cLogLuvM * value.rgb;
90: 	Xp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );
91: 	vec4 vResult;
92: 	vResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;
93: 	float Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;
94: 	vResult.w = fract( Le );
95: 	vResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;
96: 	return vResult;
97: }
98: const mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );
99: vec4 LogLuvToLinear( in vec4 value ) {
100: 	float Le = value.z * 255.0 + value.w;
101: 	vec3 Xp_Y_XYZp;
102: 	Xp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );
103: 	Xp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;
104: 	Xp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;
105: 	vec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;
106: 	return vec4( max( vRGB, 0.0 ), 1.0 );
107: }
108: vec4 mapTexelToLinear( vec4 value ) { return LinearToLinear( value ); }
109: vec4 matcapTexelToLinear( vec4 value ) { return LinearToLinear( value ); }
110: vec4 envMapTexelToLinear( vec4 value ) { return RGBEToLinear( value ); }
111: vec4 emissiveMapTexelToLinear( vec4 value ) { return LinearToLinear( value ); }
112: vec4 lightMapTexelToLinear( vec4 value ) { return LinearToLinear( value ); }
113: vec4 linearToOutputTexel( vec4 value ) { return LinearTosRGB( value ); }
114: 
115: #ifdef USE_ENVMAP
116: 	uniform float envMapIntensity;
117: 	uniform float flipEnvMap;
118: 	uniform int maxMipLevel;
119: 	#ifdef ENVMAP_TYPE_CUBE
120: 		uniform samplerCube envMap;
121: 	#else
122: 		uniform sampler2D envMap;
123: 	#endif
124: 	
125: #endif
126: uniform float opacity;
127: varying vec3 vWorldDirection;
128: #ifdef ENVMAP_TYPE_CUBE_UV
129: #define cubeUV_maxMipLevel 8.0
130: #define cubeUV_minMipLevel 4.0
131: #define cubeUV_maxTileSize 256.0
132: #define cubeUV_minTileSize 16.0
133: float getFace(vec3 direction) {
134:     vec3 absDirection = abs(direction);
135:     float face = -1.0;
136:     if (absDirection.x > absDirection.z) {
137:       if (absDirection.x > absDirection.y)
138:         face = direction.x > 0.0 ? 0.0 : 3.0;
139:       else
140:         face = direction.y > 0.0 ? 1.0 : 4.0;
141:     } else {
142:       if (absDirection.z > absDirection.y)
143:         face = direction.z > 0.0 ? 2.0 : 5.0;
144:       else
145:         face = direction.y > 0.0 ? 1.0 : 4.0;
146:     }
147:     return face;
148: }
149: vec2 getUV(vec3 direction, float face) {
150:     vec2 uv;
151:     if (face == 0.0) {
152:       uv = vec2(-direction.z, direction.y) / abs(direction.x);
153:     } else if (face == 1.0) {
154:       uv = vec2(direction.x, -direction.z) / abs(direction.y);
155:     } else if (face == 2.0) {
156:       uv = direction.xy / abs(direction.z);
157:     } else if (face == 3.0) {
158:       uv = vec2(direction.z, direction.y) / abs(direction.x);
159:     } else if (face == 4.0) {
160:       uv = direction.xz / abs(direction.y);
161:     } else {
162:       uv = vec2(-direction.x, direction.y) / abs(direction.z);
163:     }
164:     return 0.5 * (uv + 1.0);
165: }
166: vec3 bilinearCubeUV(sampler2D envMap, vec3 direction, float mipInt) {
167:   float face = getFace(direction);
168:   float filterInt = max(cubeUV_minMipLevel - mipInt, 0.0);
169:   mipInt = max(mipInt, cubeUV_minMipLevel);
170:   float faceSize = exp2(mipInt);
171:   float texelSize = 1.0 / (3.0 * cubeUV_maxTileSize);
172:   vec2 uv = getUV(direction, face) * (faceSize - 1.0);
173:   vec2 f = fract(uv);
174:   uv += 0.5 - f;
175:   if (face > 2.0) {
176:     uv.y += faceSize;
177:     face -= 3.0;
178:   }
179:   uv.x += face * faceSize;
180:   if(mipInt < cubeUV_maxMipLevel){
181:     uv.y += 2.0 * cubeUV_maxTileSize;
182:   }
183:   uv.y += filterInt * 2.0 * cubeUV_minTileSize;
184:   uv.x += 3.0 * max(0.0, cubeUV_maxTileSize - 2.0 * faceSize);
185:   uv *= texelSize;
186:   vec3 tl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;
187:   uv.x += texelSize;
188:   vec3 tr = envMapTexelToLinear(texture2D(envMap, uv)).rgb;
189:   uv.y += texelSize;
190:   vec3 br = envMapTexelToLinear(texture2D(envMap, uv)).rgb;
191:   uv.x -= texelSize;
192:   vec3 bl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;
193:   vec3 tm = mix(tl, tr, f.x);
194:   vec3 bm = mix(bl, br, f.x);
195:   return mix(tm, bm, f.y);
196: }
197: #define r0 1.0
198: #define v0 0.339
199: #define m0 -2.0
200: #define r1 0.8
201: #define v1 0.276
202: #define m1 -1.0
203: #define r4 0.4
204: #define v4 0.046
205: #define m4 2.0
206: #define r5 0.305
207: #define v5 0.016
208: #define m5 3.0
209: #define r6 0.21
210: #define v6 0.0038
211: #define m6 4.0
212: float roughnessToMip(float roughness) {
213:   float mip = 0.0;
214:   if (roughness >= r1) {
215:     mip = (r0 - roughness) * (m1 - m0) / (r0 - r1) + m0;
216:   } else if (roughness >= r4) {
217:     mip = (r1 - roughness) * (m4 - m1) / (r1 - r4) + m1;
218:   } else if (roughness >= r5) {
219:     mip = (r4 - roughness) * (m5 - m4) / (r4 - r5) + m4;
220:   } else if (roughness >= r6) {
221:     mip = (r5 - roughness) * (m6 - m5) / (r5 - r6) + m5;
222:   } else {
223:     mip = -2.0 * log2(1.16 * roughness);  }
224:   return mip;
225: }
226: vec4 textureCubeUV(sampler2D envMap, vec3 sampleDir, float roughness) {
227:   float mip = clamp(roughnessToMip(roughness), m0, cubeUV_maxMipLevel);
228:   float mipF = fract(mip);
229:   float mipInt = floor(mip);
230:   vec3 color0 = bilinearCubeUV(envMap, sampleDir, mipInt);
231:   if (mipF == 0.0) {
232:     return vec4(color0, 1.0);
233:   } else {
234:     vec3 color1 = bilinearCubeUV(envMap, sampleDir, mipInt + 1.0);
235:     return vec4(mix(color0, color1, mipF), 1.0);
236:   }
237: }
238: #endif
239: void main() {
240: 	vec3 vReflect = vWorldDirection;
241: #ifdef USE_ENVMAP
242: 	#ifdef ENV_WORLDPOS
243: 		vec3 cameraToFrag;
244: 		
245: 		if ( isOrthographic ) {
246: 			cameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );
247: 		}  else {
248: 			cameraToFrag = normalize( vWorldPosition - cameraPosition );
249: 		}
250: 		vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
251: 		#ifdef ENVMAP_MODE_REFLECTION
252: 			vec3 reflectVec = reflect( cameraToFrag, worldNormal );
253: 		#else
254: 			vec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );
255: 		#endif
256: 	#else
257: 		vec3 reflectVec = vReflect;
258: 	#endif
259: 	#ifdef ENVMAP_TYPE_CUBE
260: 		vec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );
261: 	#elif defined( ENVMAP_TYPE_CUBE_UV )
262: 		vec4 envColor = textureCubeUV( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ), 0.0 );
263: 	#elif defined( ENVMAP_TYPE_EQUIREC )
264: 		vec2 sampleUV;
265: 		reflectVec = normalize( reflectVec );
266: 		sampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;
267: 		sampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;
268: 		vec4 envColor = texture2D( envMap, sampleUV );
269: 	#elif defined( ENVMAP_TYPE_SPHERE )
270: 		reflectVec = normalize( reflectVec );
271: 		vec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );
272: 		vec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );
273: 	#else
274: 		vec4 envColor = vec4( 0.0 );
275: 	#endif
276: 	#ifndef ENVMAP_TYPE_CUBE_UV
277: 		envColor = envMapTexelToLinear( envColor );
278: 	#endif
279: 	#ifdef ENVMAP_BLENDING_MULTIPLY
280: 		outgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );
281: 	#elif defined( ENVMAP_BLENDING_MIX )
282: 		outgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );
283: 	#elif defined( ENVMAP_BLENDING_ADD )
284: 		outgoingLight += envColor.xyz * specularStrength * reflectivity;
285: 	#endif
286: #endif
287: 	gl_FragColor = envColor;
288: 	gl_FragColor.a *= opacity;
289: #if defined( TONE_MAPPING )
290: 	gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );
291: #endif
292: gl_FragColor = linearToOutputTexel( gl_FragColor );
293: }

image

Please try to update your Intel GPU driver. We had a similar report at github some time ago and this solved the issue: