-=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- (c) WidthPadding Industries 1987 0|434|0 -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=-
SoCoder -> Blogs Home -> Blogs

Created : 12 May 2013
Edited : 12 May 2013
System : Mac

Snow: More Material Junk

So, one of the big hurdles in getting my engine up and running was that I'm a picky jerk about just drawing squares on-screen to show things. This is a terrible habit, but I can't break it, so I've been working on trying to get various bits of the renderer working nicely. The big part of this is just materials - in other words, the visual properties of surfaces along with their not-quite-visible properties.

Originally, the engine recognized only two material properties: 'map' and 'shader'. These corresponded to the diffuse texture and the GLSL program respectively. The way programs are defined is the same as in the last post about this, but materials have gotten a bit of an overhaul.

Originally, the material class looked like this:

The purpose being that specific material types would inherit from it. The initial implementation only had a single subclass: rmaterial_basic_t. That handled setting the uniforms and so on, and worked alright until it turned out I needed to start loading materials from disk. Generally speaking, this is probably the better route, but it also means the overhead of reading the VMT and makes the compiler's job harder when it comes to optimization. Plus, again, there's that whole loading materials issue - it's very tedious writing different parser cases for each kind of material type. So, this setup was axed in favor of a general multi-pass material.

Now, the material type looks like this, sans pass structure body (it's just POD struct that contains various state data, nothing special):

A couple points on the differences:

  1. The class is no longer just a pure virtual interface for other materials. As I mentioned above, the material was changed to handle most visible state. The material's prepare_pass function applies a delta of the current pass and the previous pass to minimize state change, so the only downside here is that the pass type is fairly heavy in terms of memory use (this is mostly due to texture unit state).
  2. On the topic of virtual bits of the class, nothing in it is virtual anymore. This should help the compiler decide how it wants to handle things henceforth. Also, the class is marked final (a C++11-ism), so if anything tries to inherit from it, that should fail.
  3. The two omni-present uniforms, the projection and modelview matrices, are now stored as static data, so their setters are static as well. This thankfully means each material isn't carrying around its own matrices, so that's 64 to 128 bytes that get stripped, thankfully.

Ultimately, though, the restructuring around making passes a generic part of the material led to some changes in the material definition format. So, where once you had just 'map' and 'shader', you've now got something like this:

The shader definition is omitted, but now you've got passes in the material definitions. There can theoretically be as many as you want, but I've hard-limited it to four passes in the material mainly to avoid cache misses while working with the pass data and allocations that might otherwise occur when resizing an array or vector of pass data.

Each pass currently holds eight texture units. I'll probably reduce that to three or four in the future, but it depends on what the game ends up needing. I have zero qualms about hard-coding limits like that. So, you can have multiple blocks defining texture units (they must be within { ... } blocks just because that's how texture units are parsed in this case) with their maps, filters, and clamp parameters. I was sort of hesitant to allow per-pass alteration of texture parameters, but the cost of doing so is low (the textures themselves cache state to avoid unnecessary GL calls) and it only alters the way shaders access the texture data, so it's not very expensive.

The nice thing is that now I can define a material for almost any purpose, be it the shadow material (which is mostly empty - it has only a single no-depth stencil-write pass) or the console background or the UI fonts or whatever. Again, I'm usually against really general-purpose stuff because I think it leads to slower code and odd limitations, but, for now, this works pretty well. That said, the code isn't yet trying to be completely general-purpose, since I've still got it tailored specifically to my needs right now, so I guess it's like a best-of-both-worlds situation.