On the Client Code: Rendering

Forum for game modifications and custom clients.

On the Client Code: Rendering

Postby loftar » Tue Jul 08, 2014 11:03 pm

I had an explanation of the client's rendering infrastructure requested via PM, so I thought I'd write briefly about it in here in case anyone else might also find it useful. It won't be a complete description of the rendering system, but should hopefully contain enough of an introduction that you can discover the rest at your leisure.

1. State tracking
At the heart of the drawing code (be it 2D or 3D) lies the GLState class, whose purpose it is to track OpenGL states in general. Each state (which could be eg. whether depth testing is enabled, the current texture, the lighting mode, the "model" matrix, or other such things) it tracks is identified by a GLState.Slot, and a GLState.Buffer is a collection of active states for all such slots. What is important to note is that a GLState.Buffer is effectively a complete description of the entire OpenGL state (or the subset of it that the client actually uses, at least). It describes not only a single setting (that's what a Slot does), but the aggregate of all settings.

The value of each slot is either null or an object of some subclass of GLState -- for instance, in the slot for tracking the material colors, there is an instance of the Material.Colors class. The most important functions of a GLState class are the apply and unapply functions, which make the actual OpenGL calls to make the state active or, respectively, to reset the OpenGL state to what it looked like previously.

The GLState.Applier, of which there is normally only one instance (maintained by the HavenPanel), keeps a private GLState.Buffer for the states that are currently applied to the OpenGL context. It can be given a new buffer in order to switch to the state described therein, and will then compute the minimal differences between those two buffers and make the least possible changes to the OpenGL state required to reach the new state from the old one, thereby attempting to minimize the number of actual OpenGL calls. Its core function, so to speak, is to call unapply on the states that are about the change, and then call apply on the states that are changed into. However, the order of the slots matter, and a slot can declare that it depends on other slots, in which case it will be considered as having changed if any of the slots it depends on has changed, even if it hasn't actually changed itself. An example of this might be the OpenGL "viewport" setting of a 3D-rendering widget (like the map view or the avatar view), which overrides the viewport setting of the underlying 2D drawing. Therefore, if the underlying 2D draw-state has changed, and therefore applies its own viewport setting into OpenGL, the 3D widget's viewport setting needs to be reset in order for it to override the 2D state rather than the other way around. Thus, the slot for the 3D state declares that it depends on the slot for the 2D state, and the Applier takes that into account.

2. Abstract states
Some GLStates do not meaningfully override the apply and unapply functions*, but instead does its meaningful work in the prep function. The purpose of the prep function is to "insert" the GLState that it is called on into a given GLState.Buffer (passed as the argument). The concrete states, like textures, lighting modes and whatnot, simply insert themselves into whatever slot they belong in, but abstract states take this opportunity to insert other states instead. An example of this are material descriptions, which may decide to prepare such states as a specific texture, some lighting parameters or such.

3. 3D rendering procedure
All widgets that do 3D rendering are subclasses of the PView widget (which originally stood for "Perspective view", but which naming is now obsolete, as they can use any kind of projection mode), which contains implementations for basic 3D states like depth testing, face culling, viewports, scissors and whatnot, and some other utility functionality. Perhaps most importantly, it also maintains a RenderList instance. Rendering a 3D view follows a procedure of three basic steps:

  1. The "setup" step, wherein everything to be drawn is entered into the render-list;
  2. The "sort" step, wherein the render-list is finalized, most importantly by sorting according to certain criteria the order in which things are drawn; and
  3. The actual "draw" step, wherein the things in the render-list are actually drawn to the screen (only this last step actually uses OpenGL).
3.1. Setup
The basic task of the "setup" step is to add instances of the Rendered interface to the render-list; each along with a corresponding GLState.Buffer describing the state with which they are to be drawn. However, it is important to note that, just as with GLStates, there are "concrete" and "abstract" variations of those. Each Rendered has a draw function and a setup function, and when adding things to the render-list, the render-list calls the setup function on the Rendered in question in order to request it to set itself up. The setup function returns a boolean indicating whether or not its draw function should actually be called when the time comes around to draw stuff, so an abstract Rendered may simply decide to add other stuff to the render-list and return false.

The point of this procedure is partly that a single Rendered may in the end set up several things to be drawn, and partly that a Rendered may also request extra GLStates to be prepared for the things which are finally drawn; and in such a way, a chain of abstract renderables may be used to recursively build up the final state. As an example of this, consider a sword held in a player's hand: The map-view itself requests the player object (a Gob instance) to be set up for rendering; that Gob adds its position to the current GLState.Buffer being prepared, and then asks the Composite that makes up the player avatar to set itself up in its place; it, in turn, adds all the "equipped" sprites of that player, adding their individual locations in relation to the player's skeletal pose, and then asks the individual sprites to set themselves up; the sword sprite itself is a GLState.Wrapping, which applies the material (texture and lighting settings) of the sword, and only then, finally, asks the sword mesh (a FastMesh instance) to set itself up inside all that prepared state. Only that final FastMesh actually returns true from its setup function, indicating that it will actually be drawn.

3.2. Sort
In the "sort" step, the most important thing that happens is that the things to be rendered are sorted according to various criteria. The most important of these are the Rendered.Order state, which describes an enforced rendering order. For instance, transparent things need to be drawn after non-transparent things (and in back-to-front order).

3.3. Draw
In the "draw" step, the list of things to be rendered (those that returned true from their setup function during setup) is simply iterated through, their corresponding GLState.Buffer applied, and their draw functions called. This is where OpenGL is actually being used.

4. Post-script
State-tracking and the setup step of rendering are obviously those sections which I have afforded the most text, which is because that's where the client adds the most of its own structure outside of pure OpenGL. The "draw" step itself, or the apply functions of the various states, are obviously what produce the actual output on the screen, but since all they do is call OpenGL functions to do the actual work, most of that which is important is already covered in OpenGL documentation, so go Google dat ****.

I think, therefore, that this should cover the basics for understanding how the client does rendering. If there's something you're still wondering about, reply with a question and I'll expand on the above if I feel that it deserves it.

* They are abstract and thus need to be overridden, but the implementation simply throws an exception if they are actually called
User avatar
loftar
 
Posts: 1021
Joined: Sun Jul 08, 2012 7:32 am
Location: In your character database, shuffling bits

Re: On the Client Code: Rendering

Postby JohnCarver » Tue Jul 08, 2014 11:10 pm

TL:DR ¦]
ceedat wrote:the overwhelming frustration of these forums and the unnecessarily over complicated game mechanics is what i enjoy about this game most.

Nsuidara wrote:it is a strange and difficult game in no positive way
User avatar
JohnCarver
Site Admin
 
Posts: 6826
Joined: Fri Jun 06, 2014 3:02 am

Re: On the Client Code: Rendering

Postby Feone » Tue Jul 08, 2014 11:19 pm

Much appreciated.
Feone
 
Posts: 810
Joined: Tue Jan 01, 2013 8:38 pm

Re: On the Client Code: Rendering

Postby Kandarim » Wed Jul 09, 2014 7:26 am

Thanks for the explanation. Good to have it written out clearly like that. I tend to stay far away from rendering code :)
I have neither the crayons nor the time to explain it to you.
JC wrote:I'm not fully committed to being wrong on that yet.
User avatar
Kandarim
Customer
 
Posts: 5321
Joined: Mon Jan 21, 2013 4:18 pm


Return to Artifice & Arcana

Who is online

Users browsing this forum: No registered users and 12 guests