4

For educational purposes I need to compare the performance of WebGL with OpenGL. I have two equivalent programs written in WebGL and OpenGL, now I need to take the frame rate of them and compare them.

In Javascript I use requestAnimationFrame to animate, and I noticed that it causes the frame rate to be always at 60 FPS, and it goes down only if I switch tab or window. On the other hand if I always call the render function recursively, the window freezes for obvious reasons.

This is how I am taking the FPS:

var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '450px';
stats.domElement.style.top = '750px';

document.body.appendChild( stats.domElement );

setInterval( function () {

    stats.begin();
    stats.end();
}, 1000 / 60 );

            
var render= function() {
    requestAnimationFrame(render);
    renderer.render(scene,camera);
}

render();

Now the problem if having always the scene at 60 FPS is that I cannot actually compare it with the frame rate of OpenGL, since OpenGL redraws the scene only when it is somehow modified (for example if I rotate the object) and glutPostRedisplay() gets called.

So I guess if there is a way in WebGL to redraw the scene only when it is necessary, for example when the object is rotated or if some attributes in the shaders are changed.

Community
  • 1
  • 1
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187

3 Answers3

7

You can't compare framerates directly across GPUs in WebGL by pushing frames. Rather you need to figure out how much work you can get done within a single frame.

So, basically pick some target framerate and then keep doing more and more work until you go over your target. When you've hit your target that's how much work you can do. You can compare that to some other machine or GPU using the same technique.

Some people will suggest using glFinish to check timing. Unfortunately that doesn't actually work because it stalls the graphics pipeline and that stalling itself is not something that normally happens in a real app. It would be like timing how fast a car can go from point A to point B but instead of starting long before A and ending long after B you slam on the brakes before you get to B and measure the time when you get to B. That time includes all the time it took to slow down which is different on every GPU and different between WebGL and OpenGL and even different for each browser. You have no way of knowing how much of the time spent is time spent slowing down and how much of it was spent doing the thing you actually wanted to measure.

So instead, you need to go full speed the entire time. Just like a car you'd accelerate to top speed before you got to point A and keep going top speed until after you pass B. The same way they time cars on qualifying laps.

You don't normally stall a GPU by slamming on the breaks (glFinish) so adding the stopping time to your timing measurements is irrelevant and doesn't give you useful info. Using glFinish you'd be timing drawing + stopping. If one GPU draws in 1 second and stops in 2 and another GPU draws in 2 seconds and stops in 1, your timing will say 3 seconds for both GPUs. But if you ran them without stopping one GPU would draw 3 things a second, the other GPU would only draw 1.5 things a second. One GPU is clearly faster but using glFinish you'd never know that.

Instead you run full speed by drawing as much as possible and then measure how much you were able to get done and maintain full speed.

Here's one example: http://webglsamples.org/lots-o-objects/lots-o-objects-draw-elements.html

It basically draws each frame. If the frame rate was 60fps it draws 10 more objects the next frame. If the frame rate was less than 60fps it draws less.

Because browser timing is not perfect you might be to choose a slightly lower target like 57fps to find how fast it can go.

On top of that, WebGL and OpenGL really just talk to the GPU and the GPU does the real work. The work done by the GPU will take the exact same amount of time regardless of if WebGL asks the GPU to do it or OpenGL. The only difference is in the overhead of setting up the GPU. That means you really don't want to draw anything heavy. Ideally you'd draw almost nothing. Make your canvas 1x1 pixel, draw a single triangle, and check the timing (as in how many single triangles can you draw one triangle at a time in WebGL vs OpenGL at 60fps).

It gets even worse though. A real app will switch shaders, switch buffers, switch textures, update attributes and uniforms often. So, what are you timing? How many times you can call gl.drawBuffers at 60fps? How many times you can call gl.enable or gl.vertexAttribPointer or gl.uniform4fv at 60fps? Some combination? What's a reasonable combination? 10% calls to gl.verterAttribPointer + 5% calls to gl.bindBuffer + 10% calls to gl.uniform. The timing of those calls are the only things different between WebGL and OpenGL since ultimately they're talking to the same GPU and that GPU will run the same speed regardless.

gman
  • 100,619
  • 31
  • 269
  • 393
  • This statement: *"It would be like timing how fast a car can go from point A to point B but instead of starting long before A and ending long after B you slam on the brakes before you get to B and measure the time when you get to B."* is thoroughly confusing. That is literally how performance is measured in automotive magazines, 0-60-0 time and breaking distance. As long as the procedure is the same among all cars tested it is a valid measurement methodology and useful for comparison. – Andon M. Coleman Jun 12 '14 at 18:55
  • 1
    If you want to know how fast a car can accelerate AND STOP then yes. That's how you'd time it. Similarly if you want to know how fast a certain API can render *AND STOP*. But that's not what you wanted. You wanted to the the performance. That's how fast, NOT how fast + stop. What if one car it takes 2 seconds of accelerating + 3 seconds of breaking. And on the other car it takes 3 seconds of accelerating and 2 second of breaking? The only data you have is both cars took 5 seconds. That doesn't tell you which car performs faster. One of those cars actually went faster than the other. – gman Jun 12 '14 at 19:02
  • 2
    And no, that is not how car performance is measured. Go watch a race sometime. The car goes around the first lap, timing is started at the start of the second lap passing the starting point going full speed. The clock is stopped at the end of the second lap with the car still going full speed as it crosses the finish line. That is how you find the fastest car. You don't slam on the breaks and try to stop the car at the finish line. – gman Jun 12 '14 at 19:06
  • You don't normally stall a GPU by slamming on the breaks (glFinish) so adding the stopping time to your timing measurements is irrelevant and doesn't give you useful info. You timed drawing + stopping. Just like the car if one GPU draws in 1 second and stops 2 and another GPU draws in 2 seconds and stops in 1, your timing will say 3 seconds for both GPUs. But if you ran them without stopping one GPU would draw 3 things a second, the other GPU would only draw 1.5 things a second. One GPU is clearly faster but using glFinish you'd never know that. – gman Jun 12 '14 at 19:10
  • What if instead of using three.js I write a plain WebGL program? May I still count the frame rate rate with the old method? – Ramy Al Zuhouri Jun 12 '14 at 19:14
  • @gman: Nobody classifies the performance of a car by who crosses the start/finish line first. Go watch qualifying some time, it is the fastest lap time. If it mattered who crossed the line first, then whoever left pit lane first would generally earn pole position. And magazines absolutely measure the 0-60-0 time, it is a better measure of overall performance than pure acceleration because it measures more of the systems on the car. Performance is measured different ways for different purposes. – Andon M. Coleman Jun 12 '14 at 19:44
  • @RamyAlZuhouri: The problem you are discussing has nothing to do with OpenGL or WebGL. Drawing occurs when you tell it to in either API, OpenGL does not "redraw the scene only when it is somehow modified" as you claim in your original question. Three.js may behave that way, but the underlying API does not. And the fundamental problem here is that VSYNC is preventing you from measuring the amount of time it takes to perform a certain sequence of operations. You are measuring the number of frames presented over 1 second rather than the length of the frames; VSYNC adds time to each frame. – Andon M. Coleman Jun 12 '14 at 20:29
  • @RamyAlZuhouri: If you cannot disable VSYNC in one of your applications, then you have to work around it. gman even discusses this in his answer (though he assigned it to the wrong thing), the part about having no knowledge of the "amount of time spent slowing down." You really have no knowledge of the amount of time that was spent blocking for VSYNC, but if you can cause both apps to wait for commands to finish before timing you will have more measurement consistency. Timer queries in regular OpenGL are generally the solution to this whole problem, but those do not exist in WebGL. – Andon M. Coleman Jun 12 '14 at 20:33
  • @AndonM.Coleman We don't measure car performance, it was just an example. We really need to test "how fast it can drive a lap without acceleration and breaking", or how much it can handle before framerate drops. That indeed is a real measure for the graphics api. – Dragan Okanovic Jun 12 '14 at 23:13
  • @AbstractAlgorith: Right, but the problem here is that VSYNC is already creating the start/stop behavior that gman is discussing whether he realizes it or not. The OP has no control over VSYNC, and thus to make the testing methodology consistent across both applications something else has to be employed or else that start/stop behavior exhibited in WebGL but not OpenGL is going to skew the results. ***That*** was my entire point, the measurement constraints between both are not the same unless you level the playing field. VSYNC is already stalling the GPU, so half of the answer is wrong. – Andon M. Coleman Jun 13 '14 at 00:01
  • VSYNC is not stalling the GPU. The GPU is free to start drawing the next frame immediately. The only thing that happens at VSYNC is swapping buffers. And even timer queries do not work cross platform. See any tiled GPU like those found in iPhone, iPad, etc. Read how they work and you'll see how timer queries aren't useful. – gman Jun 13 '14 at 02:42
  • @gman: The GPU is not free to start drawing the next frame when the CPU is blocking waiting for VSYNC and prevented from actually staging the next frame. The pipeline quickly goes empty when you finish your frames well in advance of VBLANK as in this example. And then, the only thing you wind up timing is by in large the amount of time spent waiting for VBLANK, not the amount of time spent doing any actual rendering. – Andon M. Coleman Jun 13 '14 at 11:36
  • The reason VSYNC blocks in the first place is because all of the back-buffers in the swap chain are full and GL cannot complete any operation that modifies the back-buffer without invalidating the results of a pending buffer swap. That puts the proverbial brakes on things as you would say; with no place to store the results of executed commands, even if there were additional staged frames, the GPU cannot process them. – Andon M. Coleman Jun 13 '14 at 11:47
2

You actually do not want to use framerate to compare these things because as you just mentioned you are artificially capped to 60 FPS due to VSYNC.

The number of frames presented will be capped by the swap buffer operation when VSYNC is employed and you want to factor that mess out of your performance measurement. What you should do is start a timer at the beginning of your frame, then at the end of the frame (just prior to your buffer swap) issue glFinish (...) and end the timer. Compare the number of milliseconds to draw (or whatever resolution your timer measures) instead of the number of frames drawn.

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • Note that using `glFinish (...)` here works around VSYNC for frame performance timing, but it also forces a CPU/GPU synchronization. You do not want to use this technique during normal rendering, only to time the durartion of a frame for performance comparison purposes. If this were only related to OpenGL and not WebGL (OpenGL ES) I would suggest using a timer query (OpenGL 3.3+ feature) instead, because it does the same thing much less invasively. – Andon M. Coleman Jun 12 '14 at 18:12
  • I'm using three.js and all is done abstractly at a lower level, I never swap the buffer myself. – Ramy Al Zuhouri Jun 12 '14 at 18:20
  • Even better then, just call `glFinish (...)` at the end of the function you use to create your frame and end your timer. To get your *effective framerate* (essentially what your framerate would have been without VSYNC), add up the duration of each frame until you hit 1 second and then examine the number of frames that took. – Andon M. Coleman Jun 12 '14 at 18:22
  • glFinish does not work in WebGL and has no meaning. It actually will also give the wrong results in real GL – gman Jun 12 '14 at 18:28
  • I don't even have an animation, I just continuously rotate the object and the renderer draws the scene for me. – Ramy Al Zuhouri Jun 12 '14 at 18:31
  • @gman: I was unaware of that, it is not documented as such in the WebGL specification. Can you point me to a resource where I can read more about this? – Andon M. Coleman Jun 12 '14 at 18:32
  • @RamyAlZuhouri: Then you are not discussing WebGL here, but rather something layered on top of it. If `renderer.render(scene,camera);` merely defers rendering to some unknown point in the future, there is really no way to quantitatively measure performance. – Andon M. Coleman Jun 12 '14 at 18:37
  • It does not defer the rendering in the future, it draws the scene synchronously. – Ramy Al Zuhouri Jun 12 '14 at 18:41
  • Oh, I get you now. Three.js is a scene graph, and you have no control over the inner workings of its renderer. Comparing performance of that versus GLUT, where you have a callback that sets up the frame is really not going to be possible. You cannot get the same timing granularity in Three.js as GLUT since you cannot break the `renderer.render (...)` operation into its constituent parts :-\ – Andon M. Coleman Jun 12 '14 at 18:49
1

The correct solution is to use the ANGLE_timer_query extension when available.

Quoting from the specification:

OpenGL implementations have historically provided little to no useful timing information. Applications can get some idea of timing by reading timers on the CPU, but these timers are not synchronized with the graphics rendering pipeline. Reading a CPU timer does not guarantee the completion of a potentially large amount of graphics work accumulated before the timer is read, and will thus produce wildly inaccurate results. glFinish() can be used to determine when previous rendering commands have been completed, but will idle the graphics pipeline and adversely affect application performance.

This extension provides a query mechanism that can be used to determine the amount of time it takes to fully complete a set of GL commands, and without stalling the rendering pipeline. It uses the query object mechanisms first introduced in the occlusion query extension, which allow time intervals to be polled asynchronously by the application.

(emphasis mine)

The Fiddler
  • 2,726
  • 22
  • 28
  • That extension only pertains to one implementation of WebGL though. I already brought that up in first comment supplemental to my answer. – Andon M. Coleman Jun 13 '14 at 11:40
  • It is available on both Chrome and Firefox, and is the only way to gather accurate timing information on those browsers. – The Fiddler Jun 15 '14 at 17:07
  • ANGLE only works in Windows, and it is *optional* in Chrome. Chrome can also communicate directly with an OpenGL GPU instead of layering itself on top of D3D using ANGLE. That was my point. – Andon M. Coleman Jun 15 '14 at 18:16
  • ANGLE is always enabled, unless chrome is launched with `chrome.exe --use-gl=desktop`. I have not verified this, but I wouldn't be surprised if ANGLE_timer_query was available on non-Windows platforms, despite the lack of ANGLE. – The Fiddler Jun 15 '14 at 20:14
  • I can verify for you that `ANGLE_timer_query` is not available in the latest versions of Internet Explorer on Windows or Chrome/Safari/Firefox on OS X 10.9. Do not get me wrong -- your answer is perfectly valid on a handful of Windows browsers -- it is just that timer queries are not available on any other WebGL platform at the moment :-\ – Andon M. Coleman Jun 15 '14 at 22:37
  • In fact, on Chrome on Windows I do not even have support for `ANGLE_time_query`, only `ANGLE_instanced_arrays`. – Andon M. Coleman Jun 15 '14 at 22:46