Categories

RSS

So we’re doing another one of these things. I know this isn’t as fun as a Good Robot update, but hopefully this can keep us amused until the project gets moving again. While working on this, I found an interesting wrinkle that gives us a reason to talk about poly count vs. fill rate and why a full-blown 3D game runs many times faster than a trivial screensaver.

But first let’s talk about what I’ve made…

Let’s say we generate a simple texture that’s pure white in the center and fades to black at the edge to make a nice blurry circle.

Now let’s slap that texture on a quad (a pair of triangles, really) defined by points a, b, c, and d. We deform this polygon by making opposing points a and d vary their distance from each other according to a sine wave. Likewise, b and c move towards and away from each other on another sine wave with a longer wavelength.

We want to move this polygon around the screen.

Like this, only with an additional layer of complexity.

We DO want the movement to be chaotic and unpredictable. We DON’T want the movement to be sharp or jagged. If it moves randomly, it will just make a zig-zagging scribble all over the place. So instead we create a three-body system. (Not like the three-body problem they have in physics, although I guess there are superficial similarities between the two.) We put our polygon at point P1. P1 orbits point P2, which in turn orbits point P3, which orbits the center of the screen. The effect is a bit like a spirograph with an extra layer. This produces large motions that will (over time) send our polygon all over the screen, and do so in sweeping motions instead of abrupt ones.

The path it takes looks like this:

This is a composite (created just for the purpose of this explanation) of what the polygon is doing over about a minute of running time. It’s bright where the poly is moving slow, and dim where it’s moving fast.

Actually, we’re not just moving a single polygon around. We’re creating a trail of them. As the polygon moves around, it leaves behind a copy. These copies gradually move towards the camera, which makes them seem to grow larger as they fade out. Every few seconds we pick a new random color for our polygon.

During rendering, each copied polygon is drawn on top of the previous one, slightly brightening the pixels there. There are a total of 256 polygons in this trail. And so we end up with this blob of undulating, wobbling color that looks kind of like a slow-motion flame or vapor.

Well, I guess we should say there are 512 triangles, since that’s what really matters. In any case, the screensaver dawdles a bit. We’re only drawing 512 triangles. Why is it so slow?

Back in 2004, Doom3 gave us characters that weighed in at about 1,500 or so. For example, the imp…

…was the simplest foe in the game at just 1,100 triangles. So that imp has more than twice as many triangles as my screensaver. And the imp is being drawn using monumentally complex (by comparison, anyway) shaders that do lighting, shadowing, specular gloss, texturing, and bump-mapping. Meanwhile, my screensaver is drawing simple unlit no-shader polygons. And Doom3 shows us not only an imp, but several. Plus all the level geometry. Plus the moving machinery in the room. Plus all the other crap like the HUD and the player’s arms and weapon. And it did all of this on 2004 level hardware at a decent framerate.

Meanwhile, my screensaver chugs on my not too shabby machine. Why can a 2004 game draw many more (perhaps an order of magnitude more) triangles with far more demanding processing faster than my 2013 machine can draw this tiny handful of colored circles?

It’s all about fill rate.

How much of the screen are we filling with polygons? Or more accurately, how many total pixels are we drawing? Let’s assume we’re going to run Doom 3 and Plasma at the same resolution. And let’s just assume, for the sake of having a nice 16:9 ratio, that we’re running at a desktop resolution of 1600×900. (That’s probably small by today’s standards, and large by 2004, standards, and I’m not sure if that was ever a native resolution of a monitor, but whatever. I’m making a point here.) That means you’ve got a total of 1,440,000 pixels. In Doom 3, you will draw every pixel at least once. Some portion of the screen will get drawn more than once. Back to our two imps:

In the circled area, we might end up drawing some of the pixels in the red circle as many as three times. Say we draw the floor first. Fine. We’ve drawn an empty room. Then we draw the imp in the back, which ends up overwriting a little bit of the drawing we did earlier. Then we draw the imp in the front, and all of the pixels he occupies are drawn. So everywhere we have imps, pixels were drawn twice, except for where the arm of the front imp covers that leg of the rear one. Those pixels were drawn three times.

There are, in a broad sense, two stages to rendering. The first is vertex processing: Considering where this imp is in the scene, will it appear on screen, and if so, where? The second is pixel processing: Now that we know what regions of the scene are occupied by the imp, let’s painstakingly go over it a pixel at a time and draw it. For the curious: These two steps are what make up “vertex shaders” and “pixel shaders” that us programmers are always on about. The second step is way, way more expensive. A triangle only has three vertices, but can contain hundreds or even thousands of pixels, and in a game like Doom 3 a pixel is probably more computationally expensive than a vertex. (To draw a pixel you have to do multiple texture lookups to get the texture, specular, and bump map, and then do a page or so of math to turn that into your final color value.)

So the polygon count of our scene basically doesn’t matter. Both of the programs we’re discussing are drawing a trivial number of polygons as far as my graphics card is concerned.

You’ll notice that in my program, polygons don’t occlude each other. It’s not like I’ve got solid imps blocking our view of the scenery. It draws one polygon, and then another on top of that, and another on that. Each time it draws a polygon it must draw all the pixels, adding to what’s already been drawn. It has to. That’s how this effect is achieved.

Let me do a quick test…

That’s a single test polygon exactly halfway through its lifespan, rendered without the fuzzy texture so we can see its shape. I took a few screenshots like this, and this is a very typical sample. Running at our test resolution, it takes up about 176,319 pixels, or ~12% of the area of the screen. So each of our 256 polygons draws, on average, 12% of the screen pixels.

In the Doom example, we rendered every pixel once and a few unlucky ones got drawn a couple of times. Even in our worst-case scenario, I doubt our overdraw was anywhere near double the total pixel count. On average, every pixel is drawn slightly more than once.

In the case of my screensaver, every screen pixel pixel is drawn an average of…

0.12 x 256 = 30.72

…thirty times.

And that’s what makes it slow.

Having said all that, I accomplished what I set out to do, which was smear some pretty colors all over the place. There are a lot of things you could do to speed this up, but this effect is fundamentally mean to our existing graphics technology. It actually runs fine on one screen, but in my dual-monitor setup Windows asks the screensaver to handle a gargantuan 3600×1334 window that covers both monitors, which (according to our back-of-the-napkin work above) gives my graphics card 147,529,728 pixels to draw, which is apparently unreasonable? The framerate dips to 20 or so, and the effect loses some of the magic at that speed.

So there it is. Another disposable afternoon project. As before, it’s released under a “do as you please” deal.

Even easier, actually: do not clear the color buffer every frame. Instead, render exactly 2 polygons.(or N+1 where N is the number of plasma blobs you want)

One polygon is untextured and given the clear color, with the alpha set to the rate at which you want stuff to “fade.”

The remaining N polygons are your plasma blobs.

You don’t get the “flow out into the screen” effect this way*, but it’ll run about a thousand times faster.

*You can replace the “flow into the screen” effect by rendering the entire scene to a texture every frame, slightly upscaling it so the edges are clipped off, and then applying it to your “clear” polygon. You could even have a set of sinewave values move the center and level of the zoom effect each frame.

The problem with the “render a texture and upscale” approach is that you’ll end up with everything flowing out from the center of the screen, instead of the center of each “blob”. It’s a neat effect (and used in several Windows Media Player visulization modes) but it wouldn’t be the same. I suppose you could go for a middle ground and have the baked texture upscale from the current position of the “leading edge” quad. That might give you a kind of similar effect without rendering two hundred transparent quads.

Hmm… I assumed the entire screen was being drawn in Perspective projection, so if the “blur” polygons move only along the Z axis, that would result in them drifting away from the center of the screen. Having the polygon drop “puddles” of color which then expand does present a less trivial problem.

What a coincidence, I just got back into GW2 after a year-long absence! I wasn’t even that interested the first time, but my buddy got the game and then I’ve been addicted exploring alongside him. I just helped the quaggan this weekend!

Do not make quaggan angry. You would not like quaggan when quaggan is angry.

Really? While those groups are certainly quite vocal about how games look, I can’t recall many times when anyone other than an actual developer got specific enough to mention different types of shaders. Maybe I don’t follow gaming news enough, I dunno.

Actually, the overdraw in Doom 3 is probably way higher. The Doom 3 engine implemented multi-pass lighting, so every pixel is drawn at least as many times as the number of light sources affecting it. They use an early z-only pass to reduce the cost, though.

Unless you scale up the size of the polygon for larger monitors, a bigger play area would not result in more pixels being redrawn. Sure, you would have twice the base number of pixels to draw (the black background) each frame, but since the polygon itself isn’t still 12% of a 3600Ã—1334, it should be 6% of it.

Otherwise, if you double the width of a monitor, how do you double the size of the polygon without simply stretching it out twice as wide, and thus making it ugly?

It appears that you are asking two questions. One being “Why would the number of pixels a polygon covers increase just because the screen resolution increased?” and the other being “How do you alter the screen aspect ratio without altering the aspect ratio of the polygons?”

From what I recall of the bit of programming I did, everything wants to take in the size of an object in terms of pixels. So unless he specifically coded in that his polygon is exactly 1*MAX_HEIGHT/6 tall and the same amount wide (to account for widescreen resolutions not stretching the square polygon into a wider rectangle or 4:3 resolutions squeezing it into a tall rectangle), it won’t scale in size if you went from a 1280×720 to 1600×900 resolution.

The problem shamus mentioned was that running the screensaver on his larger monitors (and having two widescreen monitors side-by-side) made for many MANY times more pixel redraws. I was pointing out how just because the square is 12% of the screen on a small monitor doesn’t necessarily mean that it will still be 12% of the screens on larger, dual monitor setups.

And while I’m at it, adding more monitors to a setup shouldn’t double the redraws either, since widening (or making it taller) shouldn’t affect the ratio of the sides of his polygon or else it turns ugly. Thus in order to have more display area still give a polygon=12% of desktop situation would mean doubling the area of the polygon. With a fatter polygon on a wider area, it would start to be constrained by the edges of two of the sides before the others, also resulting in an ugly “path” for the polygon to take.

Since I didn’t believe that he would do that, I just used (admittedly) a badly phrased question as a way of pointing out that I didn’t think his logic applied. I honestly do want to know what he changes in his program that causes it to have to “chug” more on a larger monitor and/or dual monitor setup.

The monitors aren’t perfectly side-by-side, because the physical bottom edge of one screen is lower than the edge of the other. So it’s not just “twice as wise” but also slightly taller, with a couple of “dead zone” rectangles in the lower right and upper left corners that contain pixels that are drawn but never displayed.

So the image is scaled up in both dimensions. As you say, the poly is probably covering less than 12% of the screen like this, but it’s still drawing more total pixels.

“everything wants to take in the size of an object in terms of pixels.”

Not really. We’re back to “The same way you can choose the aspect ratio in Half-Life 2.” I can’t speak for DirectX, but OpenGL asks for polygon coordinates in arbitrarily-named “Units,” and you specify the dimensions of your draw region in both pixels(when declaring the region) and units(when setting up the view frustum). It’s fairly simple to do some quick math to ensure that your coordinate grid is square.(I actually do this for both the 2D and 3D panes, ensuring they will both have a minimum grid size or field of view regardless of tall or wide aspect ratio)

My engine actually only takes one value in pixels: the window resolution. EVERYTHING else expects to be sized in Units, in the name of future-proofing the system against someone suddenly deciding to run it on a Retina display. This is a problem Apple ran into back in 2010 or so when they released the high-resolution iMac.

As for why his estimates may have been a bit excessive, it’s customary to calculate worst-case scenerios. It wouldn’t be a very good screen saver if it didn’t try to draw to every pixel on the screen at least occasionally, would it?

Without having looked at any of the source code, and having never written a screen saver, I would guess that the polygon “size” is constant, and the appearance of scale is determined by its position relative to the camera. The camera aspect ratio changes as you change resolutions, but the zoom level does not. I’d speculate that the largest dimension is used to set the “zoom”.

So, basically, I’d expect that the polygon is scaled with respect to the largest display dimension, and the aspect ratio is not accounted for. This means that a very wide display might chop off the top and bottom, or a very tall display might chop off the sides.

In any case, I highly doubt the software is “stretching” the polygons and textures to fit the aspect ratio of the screen.

Except it’s scaled to the smaller dimension instead of the larger one. I wanted to be able to specify a box in the center of the screen and know that everything inside that box would definitely be visible.

You could near enough halve the number of pixels you need to draw and make the screensaver look nice and retro by making half the horizontal lines black, like this. No idea what effect that has on the usefulness as a screensaver, though.

Some older computer games used tricks like this. Another was to copy the data from the pixels above and below alternately, making a kind of cross-stich effect. From my very basic knowledge of computers I thought it was just a space saving algorithm, but I suppose if they can draw half as many pixels that allows for drawing more complex scenes at the cost of some visual fidelity.

Hehe. Save render time by mimicking a badly focused CRT display? Brilliant! I wonder if it would make sense to do the same thing with internet streaming video. Halve the bandwidth and make it look “futuristic” at the same time! It reminds me of the “holograms” in all those old game cenimatics. Master of Orion II and Alpha Centauri come to mind.

Would it save draw time, really? Since he doesn’t have control of exactly which lines he can draw and can’t, he would still have to draw all the shapes as they are, and then draw one final time on top of every pixel some alternating line image.

I love the look, but I can’t imagine how he could program it so that it keep track of if a polygon falls across one of these black lines (and they all will) and exactly which part of every polygon would have to not be drawn multiple times per second, multiple times per polygon.

Render to texture at lower resolution, render single polygon with that texture, render second polygon with alternating black lines to hide the upscaling. Doesn’t quite cut rendered pixels in half, but it does half the number of the most expensive pixels.

Even better, scale everything half as large vertically, render it all, and then intersperse the black lines in the buffer. You spend half the time rendering, plus a little bit of time for the pixel manipulation.

It crashed a lot for me too- maybe. I’m not sure. Starting it up ended quite quickly in most cases. I chalked it up to possibly moving the mouse at the same time as activating it. I just wanted to see it in action rather than use it as I don’t use screensavers.

I actually kinda doubt it. Sure, the fewer draw calls the better (…especially in webgl, which I’ve been mucking with lately, since each draw call is moreover an API call, and all of those are relatively expensive), but it’s only 512 triangles, or 1536 vertices, each frame. The high required fill rate seems far more likely to be the issue.

Even drawing all 512 polygons in one call, the vertices are still all going to have to be downloaded to the GPU on each frame, after all. And that means the setup isn’t actually any different from the Doom 3 imp model above, which had way more vertices, times way more imps, each frame. The card can handle orders of magnitude more vertices in a frame than what this is sending, even if it is splitting them across draw calls.

(Binding data into a single buffer and reusing that buffer each frame would avoid the vertex download for each frame, and is what I’m trying to do when I — slowly — work on the webgl version of Frontier. But it’s impossible to do that when there’s no way to make all the vertices be the same from one frame to the next in some reference frame, because everything in the world is moving around relative to other points in the world. For something like a model that walks and moves its arms and such, you really have to recalculate the vertices’ locations on the CPU every frame and re-download the points to the GPU. Luckily for Frontier, there’s very very little animated content; just the avatar guy… that’s not the case here.)

The world-space coordinates are irrelevant. The only thing that matters for your vertex buffers are the ‘model-space’ coordinates.

So split the model into “torso”, “head”, “upper arm” and “lower arm” etc submodels. They can all live in the same vertex buffer to get uploaded simultaneously, just in different locations along it.

The only thing that changes for each of these submodels is the world-space position and orientation matrix. That’s a single Uniform for each model, thus trivial.

That’s obviously trivial for ‘robot’ types of movement, where the joints are ‘fixed’ and don’t alter.

For ‘organic’ movement where the joints change shape there’s two options:
a) Use the CPU to fiddle with the small part of the mesh at each joint, and upload *just* that section of the vertex buffer each frame.
(Nobody said you had to upload the whole buffer, just that small part)

b) Use a Geometry Shader to get the GPU to calculate a set of triangle strips that nicely cover the joint.

The latter will be faster, but needs you to port your existing joint-smoothing code to GLSL.

But that requires a separate draw call for each sub-model, because you have to rebind the uniform matrix for every draw call. (Not to mention recalculate it for each sub-model, on the cpu.)

You can’t have a single draw call when any of the vertices in the draw call are moving relative to any others in a way that can’t be expressed with a single uniform (whether that’s a uniform matrix or a uniform vector or a uniform value like the original Frontier’s wind oscillation uniform-float).

Although a geometry shader might be able to do it. Those also aren’t present in webgl…

From experience, I’ve done scenes with half a million or so triangles using independent draw calls. (My original terrain project, which is shown in the programming archives, if you’re curious.) It ran much faster on much older hardware.

The problem is overdraw for sure. You can confirm this by changing the source so that the window is small and you’ll see the FPS goes up.

For the record, in Good Robot I use vertex buffers and shaders. Not sure if that’s the “modern” you’re talking about.

I’m sure you’re 100% right that this could be done much faster with shaders. But remember I didn’t know what I was building when I started. I was just throwing polygons around to see what looked good. That’s much easier in immediate mode than fiddling with the pipeline between C++ and GLSL where every change requires different variables or structures. (This is especially true in my case where I have to go fishing every time I want to do something new in GLSL because the docs are in such poor shape.)

A lot of my work is prototype work, and for prototyping there’s nothing better than immediate mode. It’s why I’m so outraged that they removed glBegin from OpenGL. I can get behind the idea that you shouldn’t use glBegin in production code. But taking away the choice because it’s not “best”? That smacks of arrogance. Not everything is a shipping AAA game.

I’d say it’s genuinely worth the jump to “modern” OpenGL. The major investment is some very reusable code for loading shaders and setting uniforms (where, to be honest, all you do is call up your graphics driver and tell it to do all of the work), and find or write a vector and matrix library; the pay-off is that you get to keep as much as possible on the graphics card where it belongs, and for all its problems with documentation, glsl is much less of a pain for handling the basic bits of drawing.

On the other hand – no, it still won’t help your filtrate problem much.

Yes, that is the ‘modern’ – no use of begin/end or fixed-function pipeline.
Pretty much, OpenGL 3.2* core profile.

I see your point on ‘this is just having fun’ code of course!

Personally I’ve found glBegin/end harder to use than vertex buffers.

In one you say “Draw a triangle strip!” then squirt the vertices.

In the other you squirt the vertices once, then say “Draw a triangle strip!” whenever you want it.

I find the ‘new’ way has less of the boring boilerplate, is less likely to screw up and more closely matches the ‘object-oriented’ way that most programs are written these days.

Aside from that, it allows the GPU to do a lot of optimisations (eg out-of-order vertex processing) that would otherwise be impossible.

Removing glBegin/end simplifies the core profile of OpenGL, so easier for nVidia/AMD/Intel to test and less likely that their graphics drivers will screw it up. Again.

So I do think they were right to remove glBegin/end.
Most importantly, they did it the ‘right way’:
It was deprecated but present for several years/versions, and on desktop you can always get a context with an older profile.

The exact opposite of some other common APIs out there(!)

(Admittedly the removal of GL_QUADS was slightly annoying. Yeah, you can do the same with either a strip or a fan, but I still had to switch the vertex order around.)

* I think it’s 3.2 where geometry shaders became core, not that many things need them.

I’m afraid that article could have been summed up as “Old man yells at cloud” even when it was written.

The only reason he succeeded was that the inefficiency he introduced was swallowed up by the advances in hardware (and, ironically, the fact that OpenGL was able to abstract away all of the hardware differences)

1600*900 is still the most common resolution for laptop screens sold today, and has been for over 4 years, in the EU. So, yes, it’s often used ;-).
And I don’t know where you live these days ,but 1600*900 small? I know fairly few people with screens over 1080p high. 900 isn’t exactly especially small.

My laptop is 1920 x 1080 and my second monitor is 1920 x 1200 While these dimensions aren’t significantly larger… let me do some math…
1.4Mpx vs 2.3Mpx so about a Phi ratio. Right on the border of significance.
In any case, the resolution doesn’t matter so much as the fraction of screen space devoted to overdraw, which was the original point. Normally, your graphics card is going to be able to drive your monitor just fine.

Very nice effect. You should combine the last screensaver with this one. You travel through the tunnel for a while (be nice to see a large planet going by as you do, maybe some stars) and then you finally come out to this etc. A psychedelic trip through space and time. ;)

Should look into the screen savers that typically come packaged with Linux distros. They have one that’s similar to the plasma effect described yet it runs over 1000FPS on even previous gen graphics hardware if Vsync is disabled. I honestly don’t get why anyone in their right mind would use a heavy screensaver. The entire idea of a screensaver is that it looks cool and doesn’t cause excessive wear and tear on your system.

Hmmmm… calling it ‘plasma’ reminded me of the venerable DOS program ‘fractint’ (apparently it’s still active and maintained, see fractint.org). If you install it, remember it’s a DOS program. Press F1 to access help and use Backspace as your ‘back’ button.

It has a fractal called ‘plasma’, which is generated similar to your fractal terrain project. According to the description, “a recursive algorithm repeatedly subdivides the screen and colors pixels according to an average of surrounding pixels and a random color, less random as the grid size decreases.” If your video mode is VGA or EGA, you can turn on color cycling and with the proper selection of bright colors, you get a writhing mass of color plasma tendrils. The only thing that would make it better is if the underlying terrain slowly changed as the colors cycled.