🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Suggestions regarding rendering UI elements and text with OpenGL

Started by
14 comments, last by Shaarigan 3 years, 9 months ago

Hello friends, I got a quick question. I was looking at a nice dev update over at ThinMatrix and noticed he's making his own UI system:


Question about UI Elements:


Sounds like a nice project. What would be the best way to implement these windows? Because I could just render quads on the screen, but wouldn't that be a single draw call per each element?

I know how instancing works now, so I'm guessing I'd have to instance all the UI elements so all UI elements represent one draw call, am I right about that?

--------------------------------------------------------------------------

Question about Font Rendering:

My question regarding fonts is very similar. I noticed reading tutorials I have to make a “spritesheet” of all the font's available letters and update quads on screen with those letters (as textures).

My question is, do I also have to batch these quads so it's not a draw call per letter? What's the current go to way to render text in an OpenGL game?

Thanks again everybody, always helpful!

Advertisement

Instancing is not the right choice for 2D/sprite-rendering, as it has a lot of overhead for “meshes” that consist only of a few vertices, and will only really pay off when you instance things that at least have a few 100 vertices/object. Also, its generally just unnecessary as you can handle this much easier.

What I do is that I have a dynamic vertex buffer where I put one “point” primitive for each sprite, which I then expand to a quad with a geometry-shader. The “point” will look like this:

struct SpriteVertex
{
	float leftPos, rightPos, topPos, bottomPos;
	float leftCoord, rightCoord, topCoord, bottomCoord;
	float z, angle;
	float r, g, b, a;
};

Then, I perform some simply batching for the draw-calls. As long as there is no change in draw-state, all sprites will end up in one simple non-indexed draw call. Whenever something like texture, scissor-rect etc.. changes you have to have another draw-call, but thats not a problem (also I support sorting of batches by texture and/or z-coordinate; though UI is mostly just drawn with painters algorithm).

Now, geometry-shader probably also has an overhead, so there is even a method to do it with just a vertex-shader. But I just didn't get to doing it myself as the geometry-shader based variant is very much fast enough for my needs, and I'm drawing an entire fricking editor-gui (see screenshot below):

Hey thanks for the quick answer!

You did that interface from scratch? That's crazy! So you pretty much said F Unity and did your own thing. I'm curious to seeing more, does your engine have a website? The screenshot is kind of small. I find this kind of stuff interesting and really want to get to that level.

I'm embarrassed to say but I think I'm a bit confused, not because of your explanation, it's my lack of skill (so far). When you mention:


As long as there is no change in draw-state, all sprites will end up in one simple non-indexed draw call.


Let's say I want to draw the word “Hello”. I'd have 5 quads (5 glDrawElement calls), and if I use the same shader, same texture, does that mean there hasn't been a state change? Again my apologies for the questions.

EBZ said:
You did that interface from scratch? That's crazy! So you pretty much said F Unity and did your own thing. I'm curious to seeing more, does your engine have a website? The screenshot is kind of small. I find this kind of stuff interesting and really want to get to that level.

At the time I started, Unity was just becoming a thing. I already had some experience on my hands, and after my pretty bad experience with the Rpg-Maker XP I wasn't looking for any other 3rd-party software but decided to do my own thing (that was at the start of university where I had lots of time on my hands). UI was the first thing that I built, and while I do consider it probably the most waste-of-time I'm doing in my engine, its also taught me a lot about code-design and so on ?

I only have a very outdated website at the moment, sorry. I have a few blog-posts on here from a few years ago which are still more recent than my website, you might find those interesting. I might also get back into writing my blog at some point or update my website, but no plans so far ?

EBZ said:
Let's say I want to draw the word “Hello”. I'd have 5 quads (5 glDrawElement calls), and if I use the same shader, same texture, does that mean there hasn't been a state change? Again my apologies for the questions.

Yes, exactly. For UI I pretty much use the same shader for everything (even for Fonts, as so far I havn't been doing anything advanced like SDF which might require a different shader). The main thing changing in UI then is textures. So what you want to do is to use one or many Texture-Atlas for your UI, and then you can batch up everything pretty efficiently. A texture-atlas can be hand-created by you, or generated automatically (the latter requiring more coding but saving you lots of manual work).

Or in other words: “State” in terms of OpenGL or rendering in general are all the things you need for your draw-call to work; so everything from resources to depth/blend/rasterizer-settings. A state-change then is when this state has to change for when you render different objects. As long as you can keep the same state you can combine things into one large draw-call. And even without its best to keep state-changes to a minimum, as even separate draw-calls are faster if you change less or no state between them.

@Juliean Now I got it! I had to re-read a couple of times and think about it, but now I understand your previous message. Hey I appreciate it ?

I'll definitely look at your blog posts here at the forums. Hopefully you'll consider writing more blog posts, I like reading about projects like that. Thanks again!

Using one draw call per character is not the most efficient way to go about it, but it sure is easy to code, and it's not like it's a sin or anything LOL. :D It's how we do our font rendering. We used a 16x16 character size. We only print like a dozen characters or so per frame, so the overhead isn't too bad. The font is Windows Calibri TTF. Are you using C++?

,font stuff
A sample screenshot, with the font rendering (soul count).

@taby Hey Taby thanks for joining the conversation! You're right, per character will be a problem. I'm actually trying to figure that out right now. Making one single mesh with the texts in my scene. Oh and yeah, my little project here in written in C++. Thanks for sharing that font, you rendered that texture atlas from your engine?

In that screenshot you showed me, you're rendering those letter with 1 draw call?

I use one draw call per letter, one draw call per icon, one draw call for the red sea, and one draw call for the islands. Oh and one dozen or so draw calls for the toolbar. That sounds like a lot, but it runs nice and slick even on my pitiful AMD Ryzen 3. In any case, I notice that you use instancing, and so I’m considering going that route too. That’s why I piped up when you created this thread.

Regarding font rendering, I can see why you’d want to cache a mesh versus re-doing the geometry every frame. It’s your call, and kudos for you for taking the difficult way out.

Do you want my barebones code to do the font loading, etc? It’s in C++.

In regards to font, I personally use font just as a specialisation of normal sprite-rendering. Again I'm using the geometry-expanded point-buffer, writing each letter individually. However, even without batching at the very worst each text-element is one draw call. If I draw many texts with the same font after another, those can also be batched together.

I mean, I assume that even when drawing each letter one by one the draw-call overhead would be low because without any state changes in between, draw calls are comparably fast. Though a certain overhead is gonna be present so far very large amounts of texts I'd still try to avoid it, and if you us a technique similar to what I'm doing then really the reduction of individual draw-calls/batching of calls is extermely trivial ?

Also please let me know if you still miss some crucial information for how I'm doing things, I'm happy to explain more (I'm just trying to contain myself upfront because I tend to write way too much if unchecked :D )

This topic is closed to new replies.

Advertisement