Thursday, October 28, 2010

Staging Area

I'm now beginning to see the benefits of having an intermediate sandbox area for editing assets.

My ultimate plan is to have reference counting be used to cull assets that aren't being used at all. As I don't have reference counting in yet, through all my demos and tests, my animation, model, and texture trees are growing massive, massive amounts. However, this is exposing something fundamentally flawed with how I was approaching my asset management that reference counting would merely cover up.

How would an artist want to use this tool? (1) To create a new resource for the game and (2) to edit an existing one. But wait, what's the full process of (1)?

>>Character artist:
Hmm... does this texture work? No. I need to up the value here...

>>Animator:
My arcs look terrible there. I'm going to go find a mirror and look silly for an hour. That'll fix everything.

>>Designer:
Oooh! Lemme try that animation. Screw that one. Or this other one. How about this one? Shit I need to redo that blend between the punch and the run.

Aaaand... Well, I don't like any of this. Cancel.

An artist isn't going to start out with final assets in creating the asset. It would be a pain to go through the process of creating a "new" asset, or "editing" an old one (and cancelling the changes later) just to see if your new animation/skin/rig, etc. works.

On top of that, someone like a designer, or an engineer doing stress tests, may try out lots of assets never intended to be in the final tree. It would be foolish to even consider placing them there and not in a temporary area.

So, there's this mode that I'm calling "Demo", an option (3) really. It guarantees no changes to the dev asset tree and the baked asset tree. It's basically a sandbox mode where you don't have to specify any names, whatsoever. Just toss things in and see how they look. Don't like it yet? Rework it and toss it in, live.

With my current system, it initially assumes that all assets being tossed in are intended to be in the final tree, then pruned if nothing ends up using them. This is one step too far, really, because the final asset isn't really defined.

So, what I've come up with are three, well-defined areas:

Dev Assets
  • Workable assets completed to some degree
  • Not necessarily final, but stable stages of a work-in-progress
  • These will be the live assets that artists will modify and re-check-in

Staged Assets
  • Local working copy of an artists proposed changes to an asset
  • Can be tossed at any time and reset from what's actually in the hierarchy
  • Can be committed to Dev and Baked asset trees
  • Sourced assets must be in the Dev Asset tree for hard-linking

Baked Assets
  • Touched only by the asset tool
  • Final game versions of the files
  • Tagged with the version control revision number
  • Hard-linked to the file in the Dev Asset tree

The difference between the Staged Assets and the sandboxy "demo" view is that Staged Assets require you to be sourcing actual, registered assets. The "demo" mode is intended for informal and rapid iteration of assets. These assets can be located anywhere on the user's computer, on a network drive, or any other location accessible through a path.

Having a required Dev Asset tree guarantees everyone has proper access to all asset files (none of them will get lost on a large team), automated rebaking is guaranteed, and a lookup of an asset that's in-game to its corresponding Max or Maya file is painless.

Wednesday, October 20, 2010

All is Fair in Love and Debugging

lol, hacks. I was needing to make sure that a function was only being called from two unique places. Since it was an operator, RMB > Find All References in Visual Studio wouldn't work so I just grabbed the first two unique return addresses and broke if it wasn't either of those.
//Make sure there are only two calling functions:
unsigned lEIP = 0;

//return address is [EBP + 4]
__asm
{
  mov ecx, [ebp + 4]
  mov [lEIP], ecx
}

static unsigned lFirstEIP = lEIP;

if(lFirstEIP != lEIP)
{
  //There should only be two functions that call this:
  static unsigned lSecondEIP = lEIP;

  if(lEIP != lFirstEIP && lEIP != lSecondEIP)
    __debugbreak();
}

Just goes to show that it doesn't matter what types of hacks you do while debugging. Do anything it takes to get the information you need. In debugging, there's no such thing as a bad hack :)

EDIT:
Oh yeah, and I also found out a while ago about the _ReturnAddress() intrinsic and StackWalk64(). These are MUCH more reliable tools to acquire this information.

Tuesday, October 19, 2010

Iterative Debugging

Sometimes when faced with a bug, I find it difficult to not plow right through, trying to figure out what logic is wrong. This fine, but needlessly slow compared to following some basic debugging patterns.

Let the logic tell you what logic is wrong. So many solutions to debugging come from binary searches. For example, binarily replacing changed code until you pinpoint what's wrong, binary searching revisions until you find when the bug was introduced, binarily (and recursively) blocking off chunks of code with a profiler to find the origin of a bottleneck.

What caused me to remember this (for the umpteenth time) now, was that I was able to use Ocaam's Razor on two seemingly identical pieces of code:

This works (copy-pasta from an example):
oQ.X() = (float) (lT0*iQ0.GetX() + lT1*iQ1.GetX());
oQ.Y() = (float) (lT0*iQ0.GetY() + lT1*iQ1.GetY());
oQ.Z() = (float) (lT0*iQ0.GetZ() + lT1*iQ1.GetZ());
oQ.S() = (float) (lT0*iQ0.GetS() + lT1*iQ1.GetS());

This doesn't:
oQ = lT0*iQ0 + lT1*iQ1;

By pairing down the problem set, half of the function by half of the function, I was able to determine in log2(n) steps (where n is a factor of lines of code) where my exact problem is. Now I know that either

  • my scalar multiplication of Quaternions is broken or
  • one of my constructors is broken
After double-checking my constructors and multiplication operators, it was a faulty Normalization function that was occurring each time a Quaternion was constructed. If I had just stared at it, I might have made the assumption that it was fine simply because it mathematically made sense.


Let logic logic for you.

And this is why your API should fail violently...

Animations aren't working any more. Crap, what changed? Let's see... I reorganized my animation system. Shit, I probably changed something wrong.

Debug. Debug. Step through. Print matrices. Wait... all the keyframes have the same transforms. What? Oh! Am I serializing out BoneTransform[0] instead of BoneTransform[i]? No... no because all the time deltas are correct.

It must be in the keyframe generation code. Damn, what did I change in there? Hmmm... well I changed the animation's name to something more reasonable than "Take001". But that wouldn't affe-- oh wait:

//Should be lTakeNames[i]->Buffer(),
//but this is always dumb: "Take001".
lAnimation->SetName(lFile.FileName());

//...

//Recalculate the animations of each bone:
for(unsigned j = 0; j < mSkeleton->mFbxBones.size(); ++j)
{
  CalculateKeyFrames(mSkeleton->mFbxBones[j],
  lAnimation.GetName().c_str(),
  lAnimation->GetTimelines()[j],
  mAxisConvertor,
  lStart,
  lKeyFrameTimes);
}

Shit, it's using the file name to request keyframe data instead of the take name...

//Should be lTakeNames[i]->Buffer(),
//but this is always dumb: "Take001".
lAnimation->SetName(lFile.FileName());

//...

//Recalculate the animations of each bone:
for(unsigned j = 0; j < mSkeleton->mFbxBones.size(); ++j)
{
  CalculateKeyFrames(mSkeleton->mFbxBones[j],
  //vvv THIS LINE DOWN HERE CHANGED
  lTakeName[i]->Buffer(),
  lAnimation->GetTimelines()[j],
  mAxisConvertor,
  lStart,
  lKeyFrameTimes);
}

Yes! That fixed it! ... WAIT... why the HELL did the FBX SDK not throw any warnings or assertions that I was requesting a take that didn't exist. It crashes on almost anything else that goes awry; why not this? Instead it repeatedly gave me the first keyframe on the global timeline.

Thanks, API.

Simple Bugs

This is just a quick post about a couple of silly bugs I found.


std::vector<FAIL>
In experimenting with the Ballmer Peak yesterday, I finally realized what was corrupting my serialized data. It was frustrating, because Every other value besides my animation key frames were serialized correctly. In viewing their data, only corrupt values could be seen. The correct number of keyframes were being serialized, but not the internal data.

I would watch it serialize out in one function call, and come back in in another. Different. What was different? My binary importer/exporter for my model system isn't a full-fledged serializer like my typical serialization systems. It's meant to be quick and dirty, and it bit me. It didn't support a Serialize() method on the object for recursive object definitions, it simply assumed POD types for everything hi-level which allowed what is tantamount to a memcpy.

The problem is, my data-structure contained an std::vector. I was serializing the pointers / junk members of the std::vector. This problem would have been hidden if it weren't for the fact that I was serializing data out and reading it right back it. That's because with deterministic execution and "dynamic base" turned off in your compiler, your pointers will usually end up being the same until user-input or random values interfere.


Only on the Surface
I always forget this... whenever you have any crashes related to DirectX, the HRESULTs only say "Invalid Call", crank up your DirectX warnings output in the control panel after switching to Debug runtimes.

I was getting a device reset crash in WM_EXITSIZEMOVE and for the life of me couldn't figure out what I wasn't reseting. After remembering to turn on debug runtimes, DirectX told me immediately: my render target surface wasn't being released and reacquired.

Sunday, October 17, 2010

Model Data

First it Was Easy
My Junior game, Æscher, didn't have any complex asset requirements, so my graphics engine just had managers for meshes, textures, and shaders. It then used a bucketed system to reduce the amount of context switching so that everything with the same shader was rendered, and in that context, everything with the same texture was rendered. Hardware instancing was used to make sure all object with the same mesh were rendered at once.

In a stupid-simplified version, think of it like this:
typedef std::vector<mesh> MeshBuckets;
typedef std::vector<meshbuckets> TextureBuckets;
typedef std::vector<texturebuckets> EffectBuckets;

//...
class GraphicsManager
{
  //...
  EffectBuckets mBuckets;
};

Then, you could loop through the hierarchy and keep the context switching to a minium (i.e. it's most expensive to switch shaders, followed by textures, then meshes):
void GraphicsManager::Render()
{
  for(unsigned e = 0; e < mBuckets.size(); ++e)
  {
    Effect   &lEffect = mEffectManager.GetByID(e);

    //Start shading:
    unsigned lPasses = lEffect.Begin();

    //For all TextureBuckets associated with Effect 'e':
    for(unsigned t = 0; t < mBuckets[e].size(); ++t)
    {
      //Specify the current texture:
      Texture  &lTexture = mTextureManager.GetByID(t);
      lEffect.SetTexture(lTexture.GetTexture());

      //For all MeshBuckets associated with Texture 't':
      for(unsigned m = 0; m < mBuckets[e][t].size(); ++m)
      {
        //Get the mesh for HW instancing:
        Mesh &lMesh = mMeshManager.GetByID(m);

        //Set up HW instancing stuff

        //Render each pass:
        for(unsigned p = 0; p < lPasses; ++p)
        {
          lEffect.BeginPass(p);
          
          //DirectX draw call

          lEffect.EndPass();
        }//End Passes
      } //End Meshes
    } //End Textures

    lEffect.End();
  }//End Effects
}

Not so Fast
However, with the new complexities I'm introducing, it's not as easy as that. Now I have skeletons and animations to manage as well as normal maps, per-instance materials, etc. I'll cross each bridge as I come to it, but right now I'm stuck on how I should handle my new mesh system.

My game only supports one mesh per object. I could extrapolate this to multiple meshes in an object, but that's something that can come later. It simplifies things for me to make this assumption.

A skeleton is something that each mesh needs to be skinned (if it's not static). Since there can be static objects, not all meshes have skeletons. However, for the objects that do have skeletons, I can't decide if it should even be in the Mesh data-structure or not.

Now, conceptually, yes, I should just toss it in there, because it would be silly to code the ability to "switch" skeletons on a mesh without the necessity for it. But that's not my point, my point is that for optimizations, I think it makes more sense for it to be in a separate manager (like my MeshManager) so that the HW instancing buffers can be more efficiently constructed instead of uselessly skipping over the skeleton data and nuking your cache.

Even more complex than that, is what about the animation data? I'm not sure I'm settled on it, but the solution I'm going for right now is to have the skeleton data with the mesh and have an AnimationManager. While technically this is worse for the HW instancing, it helps cache in the other areas when I need to access the skeleton for the skinning code right along side the mesh.

I should really do some profiling and try multiple ways, but first I should get it working one of the ways and then determine if it's even a bottle-neck.

So what's more important? 1) Cache, 2) memory overhead of another manager, 3) keeping things together that should conceptually be together? I know there's no true answer other than, "It depends." And that's the kicker.

First Milestone

Background
This is my final year at DigiPen in the RTIS (programmer track). I was really stoked for this year as I was getting to make a two-man game project with one of my favorite developers in my year, Ramon Zarazua. Not many students wanted to make a Senior game, they just wanted to internship-out their remaining GAM credits so I was very happy to have a team.

I was happy to have a nifty game project and a good workflow with my fellow dev. Unfortunately, as the cards would have it, Ramon hit personal issues and had to leave the team, leaving my two-man team to be just me.

Fortunately enough, however, I had already begun getting extremely excited by the content pipeline portion of our game, and had been planning out some grandiose things I would like to do, but would never be able to do since I also had a game to make.

Ramon leaving allowed me to switch gears, smoothly, into this new project. My asset management independent study is the result of this.

Content Pipeline
So, this is one of those things that there's no one definition for and, honestly, I'm not 100% sure, myself. The purpose of this project is to explore and discover that. However, I've got a descent idea of what I think a content pipeline should be and should do.

  • Material editing - An artist or designer should be able to quickly see what an asset looks like in the context of the game. No two lighting models look the same, and being able to modify shader attributes is a must for rapid iteration.
  • Animation blend editing - Blend systems tend to be per-title (from what I understand), so being able to specify it within the context of the engine is a must. There's no good way of viewing this otherwise.
  • Asset description - Creating / updating an asset and linking what model, animations, and material properties belong to it.
  • Orphaned assets - Old assets that have since been replaced by different textures take up precious space. They should be removed without human intervention.
  • Rebaking - Your model conversion system changed. Now all your assets are in an old, invalid format. You should be able to "rebake" all of these assets with 1-click. This also means a "development assets" needs to be kept as well as a "baked assets" tree.
  • Version control - Now that all your changes are made, they should be submitted to the build-server so everyone can see the modifications. Also, if this is done automatically, within the tool, the intricate details of your chosen Version Control System (VCS) don't need to be described to all of your tools' users.
First Milestone
It was strange losing some dev time to re-planning what I was going to do this semester and pitch it my teachers and the Dean's Office, but it was well worth it. This last Friday I had my first milestone presentation with Chris Peters, 1 week after I switched my plan.

It was by no means the best presentation I've given - it was pretty gut-wrenching seeing my VCS wrapper do silly things, and my drag and drop interface crash on things I was demonstrating, but overall I think it was a decent result after only spending a week on a new project.

I've got to say, it was difficult taking my old graphics engine code that assumed that all assets were statically loaded on startup and squish dynamic asset changing into it. That's part of what was causing it to crash, some of the DirectX mesh data wasn't properly being released and reacquired in the switch-over of the new asset.

Though I found a bunch of issues, and my product wasn't as strong as it could have been, I'm so very excited about this project I can't wait to fix these things and get on to user testing!

Now off to go fix some new Lost Device issues I've found... /codecodecode