ID:2042210
 
It's been quite a while since I've done an "Under the hood" post, so I thought I'd take some time to explore how graphics are processed in Dream Seeker. It's kind of a fascinating subject, and might help people get a handle on how graphics work the way they do.

Stage 1: Building the sprites

For the purposes of this post, I'm going to use the term "sprite" to refer to an individual icon that gets drawn; this individual icon may be the main icon of an atom, or it may be an overlay. Internally, Dream Seeker has a struct called MapIcon that handles this sucker. The webclient has a similar class called MapAtom; the two work a little differently, but for the most part they're the same thing.

When it's time to draw the map in Dream Seeker, all tiles the client should know about are queried for their turf and area icons, and also the icons of any objs and mobs on them. The webclient takes the simpler route of not bothering to do this by tile, but merely by all atoms in view; and the webclient further optimizes things by keeping a cache of already-built sprites for each atom.

In Dream Seeker, every object is turned into a series of sprites by a special routine. For an obj, that routine would be GetObjItileList(). This routine checks on any animation status to get the obj's current appearance, then calls one of two routines: GetCappearanceItileList() is the workhorse that builds all the sprites, but GetFlickItileList() will be called instead if there's a flick. (It does some intermediate processing and then calls GetCappearanceItileList() itself.) Finally, a routine is called that grabs the sprites for any images attached to this object that the client can see.

Delving into GetCappearanceItileList(), here's the gist of what it does: It looks at an appearance, and adds a sprite for the appearance's main icon to the current sprite list. Underlays and overlays also get added, by the proc calling itself recursively; underlays get added at the beginning, and overlays at the end. This proc will try to apply color and transform changes to the base appearance, depending on the appearance_flags. And if the appearance being checked uses KEEP_APART or KEEP_TOGETHER, all the sprites that are built by this call are marked so that we know where the group begins and where it ends.

Any of the routines that involve images as well will modify the results so that a KEEP_TOGETHER/APART group also includes the images added.

One minor detail that's important later: A micro-layer is included on all sprites that get built, which basically means that objects closer to the bottom left of the player's view are a teensy bit higher, as are moving atoms. This helps resolve conflicts between atoms on the same layer. The micro-layer is simply added to the sprite's layer value.

Stage 2: Sorting and culling

Once all the sprites have been built and popped into a list, it's time to do something with them.

The very first step in Dream Seeker is to look at all the KEEP_TOGETHER/APART groups and do some rearranging, so that sprites are given parent-child relationships and only the parents are left in the "real" list. (The webclient does all this at the end of the building step; it's easier to manage there.) So DS runs through the sprite list and looks for markers where groups begin, and when it finds them it looks for the end of the group. A group may contain other groups, so one sprite might be the parent of another, which is the parent of another, and so on. From this point forward, all sprites that are marked as children are ignored; they don't get sorted with the others and don't get used again until it's time to render.

Now there's the cull. Any sprites that are outside of the visible bounds (after transform) will be culled--unless they have any overlays or underlays that are in bounds. The webclient will cull any blank sprites that have no icon or maptext, but doesn't bother worrying about bounds.

After the cull, the sprites have to be sorted. The basic sort used by topdown mode looks like this, taking each rule into consideration in order:
  • Sprites on a lower plane are drawn first.
  • Sprites with PLANE_MASTER are drawn first.
  • Sprites on a lower subplane are drawn first.
    • BACKGROUND_LAYER is lower.
    • EFFECTS_LAYER is higher.
    • TOPDOWN_LAYER is higher.
    • HUD sprites are higher--unless the .dmb is older than version 510 and this is Dream Seeker.
  • Atoms with a higher layer are drawn later.
  • As a tie-breaker, atoms that came first in the sprite list are drawn first.
I'm leaving out a little extra logic that comes into play for big turf icons, but it's not that important to this concept.

Other map formats use a different sort. It's really not perfect, because they should be using topological sorting rather than a simple sort. But to explain those formats in brief, they take the atom's bounds into account in order to tell which items are nearer or farther, and only after nearness has been determined do they take layer into account. But planes and subplanes still take priority.

Stage 3: The render

Now it's time to take these icons and render them. A function called RenderAll() begins this process, by going through the list of sprites one by one. If the sprite is a plane master, then it will call RenderPlane() and that will also take care of any other sprites on that plane. If the sprite has children, RenderAtomGroup() handles it. Otherwise, we call DrawSprite().

RenderPlane() first checks to see if multiple PLANE_MASTER sprites have gotten grouped together; if so it skips over them until it comes to the last one. Then it creates a temporary surface as big as the map (including HUD expansion). It then goes through the next icons in the list, doing the same logic that RenderAll() did, until it finds a sprite on a different plane or reaches the end of the list. After all the icons have been drawn on that temporary surface, the plane's color and transform are applied, and it's drawn on top of the main scene. The master sprite's own icon, if it has one, is never drawn.

RenderAtomGroup() creates a temporary surface as well, big enough for all its children. The child sprites may have children of their own, in which case RenderAtomGroup() is called recursively; or otherwise DrawSprite() is called. DrawSprite() also gets called for the main parent icon. Once everything is drawn onto the temporary surface, the parent's color and transform are applied, and so on; you know the drill.

Finally there's the real star, DrawSprite(): It determines if the sprite needs to apply a color matrix, and draws the icon and maptext. (If there is maptext, this creates a temporary surface so the icon and maptext act like they're a KEEP_TOGETHER group.) This routine, and the one that draws from a temporary surface, also will update the blend mode and clipping as needed.

Prior to version 510, the drawing was done in two big loops: First any HUD sprites were drawn, then clipping was applied and all sprites were drawn. This was a messy setup and not very flexible.

The Wrapup

So that's how icons are drawn. A lot has changed over the years. Way back in the day, icons were always clipped to tile bounds, and if they had a pixel offset due to gliding then they got duplicated onto another tile. (This did allow for a cool effect I saw once, where SEE_TURFS was on, and mobs simply disappeared from view as they left the visible range.)

As I mentioned in a few places above, the webclient may have its own way of doing a few things; it tries to keep in sync with DS to whatever extent it can, but often the differences are minor anyway. The way the structures work in the webclient, it's way easier to keep track of parent-child relationships, so the way those are handled is different. Also the webclient has an easier time caching data that doesn't need to be re-done, so it's optimized in a way that's harder to do in Dream Seeker.

How any of this may change in the future depends partly on what new features may be added. Topological sorting for ISOMETRIC_MAP and SIDE_MAP are also highly desirable, and something I intend to do at some point.

Armed with this knowledge, you can put PLANE_MASTER and other graphical features to good use. Hopefully you also know to avoid putting too many things on the same layer when the outcome of a sort might be ambiguous. Now go and make use of these features, for glory and profit!
Very informative, more! :D
But I think if you wanted to arm them with knowledge on how to use PLANES and such an example and the example in action are the best way D:
In response to Kozuma3
The reference already has an example on PLANE_MASTER, so I'm not sure that would have added anything to this post.
In response to Kozuma3
Kozuma3 wrote:
Very informative, more! :D
But I think if you wanted to arm them with knowledge on how to use PLANES and such an example and the example in action are the best way D:

I don't really think this was a tutorial on planes but more so a way to give us devs a better look at what's really happening in our backyard, helping our decision making more with this information.
In response to Ishuri
Ishuri wrote:
I don't really think this was a tutorial on planes but more so a way to give us devs a better look at what's really happening in our backyard, helping our decision making more with this information.

Of coarse, but I was stating what I was thinking.

I think a post about PLANES would help more than how DM handles stuff that we can't mess with.
would help more than how DM handles stuff that we can't mess with.

The fact that overlays were appearances led to a huge amount of broken code in this community for decades because nobody really understood what appearances really were and what values they had.

The more you understand BYOND under the hood, the better you can take advantage of the quirks of the system.
In response to Kozuma3
Kozuma3 wrote:
I think a post about PLANES would help more than how DM handles stuff that we can't mess with.

"How DM handles stuff that we can't mess with" is more or less the whole point of the Under the Hood series. It's always good to clarify this stuff, especially for power users.
In response to Lummox JR
Lummox JR wrote:
Kozuma3 wrote:
I think a post about PLANES would help more than how DM handles stuff that we can't mess with.

"How DM handles stuff that we can't mess with" is more or less the whole point of the Under the Hood series. It's always good to clarify this stuff, especially for power users.

ofc, but this ?stems? from me posting a reply about the end of your post about "arming" them knowledge to put PLANES to use o-o.
Great tutorial I really needed this info for optimizing some client side rendering for a project I'm working on