Skip to main content
GameDev.net gamedev.net
🔒 Locked

Instancing worth it for rendering text in a 2D game?

Started by Eric F. Dec 20, 2016 at 8:21 AM 17 replies 8.1k views
Original Post
Eric F.
Eric F.

Hey guys,

I have a DX11 text rendering implementation where I have a bitmap with all the character glyphs that I use to build a list of quads with the position and UV of each glyph corresponding to the string I need to print. I then use one call to draw the text into a texture and can draw that texture wherever I need. Simple functions, simple shaders. I can also easily parse for special character, do linefeeds, limit the width for text boxes, change color, etc.

Today I sat down and decided to tackle the long overdue task of converting the rendering to use instancing, but it just hit me that it might not provide the great gains I previously thought it would. Most characters are of different sizes, so I would either have to handle resizing the geometry or maybe update the bitmap and then draw a fixed size quand and then reposition it based on the real width of the character. Or something else. This seems to break the K.I.S.S. principle.

Anyways, in the case where I do not issue more than 1 draw call to render the text, would there be any real benefits of using instancing to render text?

Thanks!

ongamex92
ongamex92

A quick test to check if any optimizations are needed - just fill the whole screen with text, and see if there is a significant slowdown.

(Just to answer you question, instancing shouldn't be needed).

Hodgman
Hodgman

Instancing does not perform well for meshes with small polygon counts -- such as a single quad. You're better off not using instancing for rendering a list of quads.

You can minimize data by only sending the quad center position X/Y coordinate and the width/height (instead of four x/y coordinates) along with a special VS that manually reads the vertex attributes from a SRV (instead of using the IA to read them automatically), or alternatively you can use the GS to convert a single vertex into four.

SubStandard
SubStandard

There's actually a much easier way to do it. When you have your final scene ready to present, you can get the Win32 device context - apologies, but it's been awhile since I used DX 11 and I can't remember the specifics. A little perusal of the various interfaces and you'll find it; there's a method to GetDC. You can get the device context (DC) from DirectX, pass that to any Windows GDI calls desired to use standard font drawing stuff - sizing, colors, you name it - then just present the scene as normal. It saves a lot of problems.

SoldierOfLight
SoldierOfLight

You're much better off using D2D/DirectWrite for D3D text rendering than GDI via GetDC.

21st Century Moose
21st Century Moose

In addition to the above advice, you're almost certainly not drawing enough text for it to be a bottleneck worth addressing.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 
Eric F.
Eric F.

A quick test to check if any optimizations are needed - just fill the whole screen with text, and see if there is a significant slowdown.

Great idea, I'll definitely do that.

In addition to the above advice, you're almost certainly not drawing enough text for it to be a bottleneck worth addressing.

Yeah, you're probably right. Besides, rendering the text in a texture would incur the rendering cost of parsing that text only once. It's probably cheaper to then just render the text texture until it's not needed anymore. I'll check that too.

@Ryan_001, thanks a lot for the link to that presentation. I found the video of that presentation on the GDC website: http://www.gdcvault.com/play/1020624/Advanced-Visual-Effects-with-DirectX along with many other great ones too! Very informative.

Thanks guys.

In addition to the above advice, you're almost certainly not drawing enough text for it to be a bottleneck worth addressing.

Yeah, you're probably right. Besides, rendering the text in a texture would incur the rendering cost of parsing that text only once. It's probably cheaper to then just render the text texture until it's not needed anymore. I'll check that too.

@Ryan_001, thanks a lot for the link to that presentation. I found the video of that presentation on the GDC website: http://www.gdcvault.com/play/1020624/Advanced-Visual-Effects-with-DirectX along with many other great ones too! Very informative.

In addition to the above advice, you're almost certainly not drawing enough text for it to be a bottleneck worth addressing.

Yeah, you're probably right. Besides, rendering the text in a texture would incur the rendering cost of parsing that text only once. It's probably cheaper to then just render the text texture until it's not needed anymore. I'll check that too.

@Ryan_001, thanks a lot for the link to that presentation. I found the video of that presentation on the GDC website: http://www.gdcvault.com/play/1020624/Advanced-Visual-Effects-with-DirectX along with many other great ones too! Very informative.

Mona2000
Mona2000

Instancing does not perform well for meshes with small polygon counts -- such as a single quad. You're better off not using instancing for rendering a list of quads.

It performs identically on the GPU and orders of magnitude faster on the CPU. How is that not better?

Hodgman
Hodgman

Instancing does not perform well for meshes with small polygon counts -- such as a single quad. You're better off not using instancing for rendering a list of quads.

It performs identically on the GPU and orders of magnitude faster on the CPU. How is that not better?

If you're comparing one instanced draw-call for all quads vs one draw-call for each quad, then sure there's a massive difference in CPU perf...
But you shouldn't use one draw-call per quad, you should use a single indexed draw-call for all quads, in which case the CPU performance is the same.

When i said 'does not perform well' I was referring to the GPU side -- instancing does incur a cost on the GPU side, especially for meshes with a small number of vertices. The alternative that I mentioned will be faster in terms of GPU time and equal in CPU time (one draw call, one buffer of per-quad attributes).

Vertex Shader Tricks by Bill Bilodeau (linked above by Ryan) has the gist of it -- Drawing quads as an indexed draw-call is much faster than an instanced draw-call in terms of GPU time:

4u4NSeq.png awOM9hk.png

I've seen this in practice too -- we saved a measurable amount of milliseconds by converting our impostor rendering system (for drawing a crowd of 100k characters) from using instanced quads to a large index list of quads -- and we didn't even do it the ideal way of having one vertex per quad (we still used the simple method of four verts per quad in the buffer and a standard VS and IA config).

Side notes from the above graph; NV GPU's seem especially sensitive to this "small-mesh instancing overhead" (this penalty goes away for meshes with ~500 verts IIRC), and NV GPU's are great at using the GS stage.

Mona2000
Mona2000

Obviously the highly specific technique for that use case is gonna be faster than the generic instancing one that doesn't allow further optimization and you should use it whenever possible, but that's not a valid comparison to show instancing overhead.

21st Century Moose
21st Century Moose

I think the flaw in the thinking here is only measuring at the front-end. I know that I certainly used to fall into that trap years ago.

The way it looks in this case is: (a) you measure the number of vertices used for an indexed or non-indexed draw, and (b) you measure the number of vertices used for a GS or instanced draw. You see that (b) is significantly lower than (a), and therefore you assume that (b) must be faster than (a).

The reality is that vertex counts are only part of what can contribute to performance, there are other factors, and depending on one's use case vertex counts may not even be relevant.

This can be counter-intuitive; there's a whole "anti-bloatware" culture based on the premise that using more memory is bad, using less must be good, and this kind of metric just flies completely in the face of it.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls. 
Hodgman
Hodgman

Obviously the highly specific technique for that use case is gonna be faster than the generic instancing one that doesn't allow further optimization and you should use it whenever possible, but that's not a valid comparison to show instancing overhead.

The statement you wanted me to clarify was: Instancing does not perform well for meshes with small polygon counts. You're better off not using instancing for rendering a list of quads. :wink:

I assumed we were both talking about the performance in this specific situation of text rendering, and that specific bit of advice, not the general case :P


Rendering a list of quads (e.g. text or billboards) is the extreme case, but the same performance pitfall applies to any low-poly model. e.g. if instancing a-few-hundred-poly models, you may find that old-school pre-HW-instancing techniques (or modern techniques that appeared after the IA stage disappeared from HW) are actually still faster than using HW instancing. At around 1k+ poly's you'll likely see no real performance overhead from instancing, making it useful.

ErnieDingo
ErnieDingo

Instancing does not perform well for meshes with small polygon counts -- such as a single quad. You're better off not using instancing for rendering a list of quads.
You can minimize data by only sending the quad center position X/Y coordinate and the width/height (instead of four x/y coordinates) along with a special VS that manually reads the vertex attributes from a SRV (instead of using the IA to read them automatically), or alternatively you can use the GS to convert a single vertex into four.


I have agree here because I've done exactly what the op originally did but i have compulsion to change to instancing. Simply because i don't get anything from the move.

1. I actually Calc the quad in screen space and store all the geometry in the cpu. This Calc only occurs when my string is updated.
2. I don't even bother passing the point only to gs to expand it. Because the difference in data is yes an order of magnitude but I'd still just bytes.
3. Instancing does give me some options like rotating quads etc. And adding instancing anyway should be trivial for this.

So in the end. I would use instancing actually not for instancing but for things like animating the quad. I found nothing performance wise with such small data sets.
Indie game developer - Game WIP    Strafe (Working Title) - Currently in need of another developer and modeler/graphic artist (professional & amateur's artists welcome)   Insane Software Facebook
Happy SDE
Happy SDE
Anyways, in the case where I do not issue more than 1 draw call to render the text, would there be any real benefits of using instancing to render text?

In my renderer I have 1 draw call for text rendering (1 draw per pass: score pass is 1 draw call, stat pass - is a second one).

I use GS for quad generation, texture has several fonts, and each letter can be in different color.

VS passes through 1 point/character {uv, screen coord, and color}

I do not use rotations or other transforms yet.

I can use mono fonts and normal fonts and combine them in one draw call.

Just measured timings in NSight for stat pass:

For 38 characters - 36 microsec,

For 122 characters - 52microsec,

for 300 characters - 66.

The other benefit for me is that I need to update instance buffer only once per draw call and only if my text has changed.

One more thought: probably rendering more than 1000 characters per frame is not very common, so examples with 500k sprites are not so relevant for this topic.

Eric F.
Eric F.

I just did some tests while I rewrote my text box parsing and rendering. This is on a Lenovo Ideapad Y560 laptop, which has a Radeon HD5730 video card.

Rendering a screen full of text at 1980x1080 (external monitor),

15750 total characters for 63000 vertices,

using a single DrawIndexed call,

with a simple pixel shader that does transparency.

The time to render the text varies between 2 and 4 microsecond. That is with no instancing and sending all those vertices to the gpu. I only update the buffer if the text changes in some way.

Strangely enough, if I render 200 characters, I get the same timing, 2 to 4 microseconds. This might be an innacuracy of the highperf counter, but I'm not sure. What I know is that its fast enough for my needs.

Happy SDE
Happy SDE



Strangely enough, if I render 200 characters, I get the same timing, 2 to 4 microseconds. This might be an innacuracy of the highperf counter, but I'm not sure. What I know is that its fast enough for my needs.

Probably you are measuring CPU time (time to prepare your commands).

GPU timings are usually measured by GPU profilers such as NSight/DX GPU query/GPUView/..

Tangletail
Tangletail

I think you're probably better off using a batching method for doing text. The only real difference is the geometry, which should be perfectly fine for creating a massive index for rendering. Even better is that you can store this index data, and then toss out the string if you're not going to change it.

Also, if you're rendering 200 characters in 2 microseconds, I think you're doing good. Remember that a micro is a fraction of a mili....

Especially when you consider the fact that if you're doing gui with scroll bars or something... more than likely you have a scissor over the window to get rid of the junk you don't care about.

Eric F.
Eric F.
Strangely enough, if I render 200 characters, I get the same timing, 2 to 4 microseconds. This might be an innacuracy of the highperf counter, but I'm not sure. What I know is that its fast enough for my needs.

Probably you are measuring CPU time (time to prepare your commands).

GPU timings are usually measured by GPU profilers such as NSight/DX GPU query/GPUView/..

Doh, very true! I'l give it a looksee.

Thanks guys. I got things working pretty fast now. If this becomes a problem, then I'll investigate.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.