I think it is commonly accepted that real time is everything that is above interactive. And interactive is defined as "responds to input but is not smooth in the fact that the animation seems jaggy".
So real time will depend on the speed of the movements one needs to represent. Cinema projects at 24 FPS and is sufficiently real time for many cases.
Then how many polygons a machine can deal with is easily verifiable by checking for yourself. Just create a little VBO patch as a simple test and a FPS counter, many DirectX or OpenGL samples will give you the perfect test bed for this benchmark.
You'll find if you have a high end graphics card that you can display about 1 million polygons in real time. However, as you said, engines will not claim support so easily because real world scene data will cause a number of performance hogs that are unrelated to polygon count.
You have:
- fill rate
- texture sampling
- ROP output
- draw calls
- render target switches
- buffer updates (uniform or other)
- overdraw
- shader complexity
- pipeline complexity (any feedback used? iterative geometry shading? occlusion?)
- synch points with CPU (pixel readback?)
- polygon richness
Depending on the weak and strong points of a particular graphic card, one or another of these points is going to be the bottleneck. It's not like you can say for sure "there, that's the one".
EDIT:
I wanted to add that, one cannot use the GFlops spec figure of one specific card and map it linearly to polygon pushing capacity. Because of the fact that polygon treatment has to go through a sequential bottleneck in the graphics pipeline as explained in great detail here: https://fgiesen.wordpress.com/2011/07/03/a-trip-through-the-graphics-pipeline-2011-part-3/
TLDR: the vertices have to fit into a small cache before primitive assembly which is natively a sequential thing (the vertex buffer order matters).
If you compare the GeForce 7800 (9yr old?) to this year's 980, it seems the number of operations per second it is capable of has increased one thousand fold. But you can bet that it's not going to push polygons a thousand times faster (which would be around 200 billion a second by this simple metric).
EDIT2:
To answer the question "what can one do to optimize an engine", as in "not to lose too much efficiency in state switches and other overheads".
That is a question as old as engines themselves. And is becoming more complex as history progress.
Indeed in real world situation, typical scene data will contain many materials, many textures, many different shaders, many render targets and passes, and many vertex buffers and so on. One engine I worked with worked with the notion of packets:
One packet is what can be rendered with one draw call.
It contains identifiers to:
- vertex buffer
- index buffer
- camera (gives the pass and render target)
- material id (gives shader, textures and UBO)
- distance to eye
- is visible
So the first step of each frame is to run a quick sort on the packet list using a sort function with an operator that gives priority to visibility, then pass, then material, then geometry and finally distance.
Drawing close objects gets prirority to maximize early Z culling.
Passes are fixed steps, so we have no choice but to respect them.
Material is the most expensive thing to state-switch after render targets.
Even in-between different materials IDs, a sub-ordering can been made using a heuristical criterion to diminish the number of shader changes (most expensive within material state-switch operations), and secondly texture binding changes.
After all this ordering, one can apply mega texturing, virtual texturing, and attribute-less rendering (link) if deemed necessary.
About engine API also one common thing is to defer the issuing of the state-setting commands required by the client. If a client requests "set camera 0", it is best to just store this request and if later the client calls "set camera 1" but with no other commands in between, the engine can detect the uselessness of the first command and drop it. This is redundancy elimination, which is possible by using a "fully retained" paradigm. By opposition to "immediate" paradigm, which would be just a wrapper above the native API and issue the commands right as ordered by client code. (example: virtrev)
And finally, with modern hardware, a very expensive (to develop), but potentially highly rewarding step to take is to switch API to metal/mantle/vulkan/DX12-style and preparing the rendering commands by hand.
An engine that prepares rendering commands creates a buffer that holds a "command list" that is overwritten at each frame.
Usually there is a notion of frame "budget", a game can afford. You need to do everything in 16 milliseconds, so you clearly partition GPU time "2 ms for lightpre pass", "4 ms for materials pass", "6 ms for indirect lighting", "4 ms for postprocesses"...