Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    132
  • comments
    99
  • views
    88985

About this blog

More sinking than swimming, really.

Entries in this blog

 

Baby Steps

Holy crap, an update! I haven't had much to say lately because work has been draining my urge to work on personal projects, but I have a few things in the works that may or may not see the light of day.

So, the subject of today's entry is multithreading. Multithreading is no longer the wave of the future, it's here and it's not going away. As such, I figured I should learn a thing or two about it. This is something I've wanted to do for a while, but have been terribly lazy about. And, you know, it's very relevant to work, so that's a plus.

That all said, I spent an hour tonight hammering out a very basic introduction for myself into the world of threading. Nothing terribly special, it's just one thread writing to a queue while another reads from it. I'll let the code speak for itself, critique it as you will.

Mutex.h

#ifndef MUTEX_H
#define MUTEX_H

#include

#define LOCK(hLock, timeout) { thread::Mutex _lock(hLock, timeout);

#define SAFE_LOCK(hLock, timeout) try { thread::Mutex _lock(hLock, timeout);

#define UNLOCK }

typedef void* HANDLE;

namespace thread
{
class WaitAbandonedException : public std::exception
{
public:
WaitAbandonedException() throw() {}
WaitAbandonedException(const WaitAbandonedException& rhs) throw() {}
WaitAbandonedException& operator=(const WaitAbandonedException& rhs) throw() {}
virtual ~WaitAbandonedException() throw() {}
virtual const char *what() const throw() {return "Wait Abandoned";}
};

class WaitTimeoutException : public std::exception
{
public:
WaitTimeoutException() throw() {}
WaitTimeoutException(const WaitTimeoutException& rhs) throw() {}
WaitTimeoutException& operator=(const WaitTimeoutException& rhs) throw() {}
virtual ~WaitTimeoutException() throw() {}
virtual const char *what() const throw() {return "Wait Timeout";}
};

class Mutex
{
public:
Mutex( HANDLE hLock, DWORD timeout);
Mutex( const Mutex& copy );
Mutex &operator=(const Mutex &rhs);
~Mutex();

bool Locked() {return m_IsLocked;}
private:
HANDLE m_Lock;
//mutable locked state because it will be changed on copy and assignment when ownership is passed
mutable bool m_IsLocked;
};
}

#endif







Mutex.cpp

#include
#include "Mutex.h"

thread::Mutex::Mutex( HANDLE hLock, DWORD timeout )
: m_Lock(hLock),
m_IsLocked(false)
{
//aquire lock on construction
DWORD Ret = WaitForSingleObject(m_Lock,
timeout);

switch (Ret)
{
case WAIT_OBJECT_0:
m_IsLocked = true;
break;
case WAIT_ABANDONED:
throw *(new thread::WaitAbandonedException);
break;
case WAIT_TIMEOUT:
throw *(new thread::WaitTimeoutException);
break;
default:
//unkown error, just bail
throw;
break;
}
}

thread::Mutex::Mutex(const thread::Mutex& copy)
{
*this = copy;
}

thread::Mutex& thread::Mutex::operator=(const thread::Mutex &rhs)
{
//release any existing lock
if (m_IsLocked)
{
ReleaseMutex(m_Lock);
}
m_Lock = rhs.m_Lock;
m_IsLocked = rhs.m_IsLocked;

//take ownership of copied mutex
rhs.m_IsLocked = false;

return *this;
}

thread::Mutex::~Mutex()
{
if (m_IsLocked)
{
ReleaseMutex(m_Lock);
}
}







thread.h

#ifndef THREAD_H
#define THREAD_H

#include

extern HANDLE g_Mutex;

namespace thread
{
DWORD WINAPI Func(LPVOID lParam);
}

#endif







thread.cpp

#include "thread.h"
#include "Mutex.h"
#include
#include
#include

extern std::queueint> g_Queue;

DWORD WINAPI thread::Func(LPVOID /*lParam*/)
{
while(TRUE)
{
SAFE_LOCK(g_Mutex, INFINITE)
while (!g_Queue.empty())
{
if (g_Queue.front() == -1)
{
return TRUE;
}
std::cout "Thread two reading " " from queue.\n";
g_Queue.pop();
}
UNLOCK
catch (std::exception &e)
{
std::cout "Caught exception in thread 2: " "\n";
delete &e;
}
}

return TRUE;
}







main.cpp

#include
#include

#include
#include "thread.h"
#include "Mutex.h"

HANDLE g_Mutex;
std::queueint> g_Queue;

void FillQueue();

int main()
{
g_Mutex = CreateMutex( NULL,
FALSE,
NULL );

DWORD threadID;
HANDLE hThread = CreateThread( NULL,
0,
thread::Func,
NULL,
0,
&threadID );

FillQueue();

WaitForSingleObject(hThread,
INFINITE);

CloseHandle(hThread);

CloseHandle(g_Mutex);

return 0;
}

void FillQueue()
{
for (int i = 0; i 100; ++i)
{
SAFE_LOCK(g_Mutex, INFINITE)
std::cout "Thread one adding " " to the queue.\n";

g_Queue.push(i);
UNLOCK
catch (std::exception &e)
{
std::cout "Caught exception in thread 1: " "\n";
delete &e;
}
}

g_Queue.push(-1);

return;
}







'Til next time!

Driv3MeFar

Driv3MeFar

 

...

Making games would be great if it weren't for the people that played them.

For real.

Disclaimer:
Making games is great. And I know that most gamers (around here at least) aren't a bunch of 13 year old fan boy douche bags. But come on. Really.

Driv3MeFar

Driv3MeFar

 

Stuff

Graduation was today. I still have a few summer classes before I'm officially done, and have been working for a year, so it doesn't mean much to me. Parents enjoyed it, I guess.

I leave on vacation tomorrow. I'll be in New Zealand for two weeks. We're going kayaking, glacier hiking, and just exploring the southern island. Should be a blast, I can't wait. Should also be a really damn long plane ride.

The most important news of late is that I got a new car on Friday. At the start of my sophomore year I leased a Mazda3, and the lease just expired. I loved that car. So much, in fact, that I just bought a brand new 2008 Mazda 3s. I got the 5-door model (which has a slightly bigger engine than my old 4-door 3i), which so far has been great. The biggest change for me is that I got a manual transmission on my new car. I've driven a manual once before, when I was like 15 and learning how to drive, for all of 20 minutes. So, the day I got it there were many stalls and lots of rolling down hills. Since I've been driving my parents around all weekend (they were in town for graduation), I'm starting to feel pretty comfortable with it now. Steep hills still scare me, though.

So, that's it. See you in two weeks, Gamedev.

Driv3MeFar

Driv3MeFar

 

I got a job, version 2

Today I accepted a full time position at the studio I'm currently an intern at. I've known they were going to offer me a job for more than a month now, but that doesn't make it any less satisfying. My official title will be Associate Systems Engineer, which means I'll be doing mostly what I do now (tools development), plus I'll be taking on some more engine-side features, which is pretty much exactly what I wanted. Any way, I'm very happy [grin].

Graduation is Sunday. I'm walking at the ceremony, but technically I still have a few summer classes to take to finish up my degree. And I go on vacation on Monday. It's a good week.

Driv3MeFar

Driv3MeFar

 

Productivity++

One of the guys at work just wrote a new tool that cut a full Win32 build from ~45 minutes to under 10. He's my new hero.

I haven't been journaling lately because I've been crazy busy. The school semester ends in a week and a half, which means its crunch time for final projects. We've been busy at work lately, with two milestones due this week, as if school wasn't enough.

Exciting (for me) news on the work front, though. As of May 1st, my internship is over, I'll be starting full time [grin]. I've been with the studio for a year now, and it's a great place to work. Very happy I've gotten the chance to work there full time, hopefully I won't make too big a hash of it all [wink].

Driv3MeFar

Driv3MeFar

 

Seattle Opera

This thread has reminded me that next year the Seattle Opera will be performing the Ring cycle. If you're interested, tickets for the general public are on sale on the web November 12th (so, this is a bit early, but I'm rather excited). If this sort of thing appeals to you, consider yourself warned: tickets tend to sell rather quickly. I'll be there, unless something unexpected crops up.

Driv3MeFar

Driv3MeFar

 

Christ

There's something I want to talk about related to this thread (and more accurately, this news story), but I think it's better said here than there.

After reading that cnet post, I noticed this response:
Bill Gates to America: F*** You! which contained such gems as:


I swear to god, people in this country are fucking retarded (I find it particularly hilarious that this jackass obviously forgot he had turned on caps lock about 2 sentences before that one). [flaming]

Edit: It appears this post has been removed by cnet. You get the gist of it, I'm sure.

Driv3MeFar

Driv3MeFar

 

I maek gaem!

I have an assignment due later today in my data compression and file formats class. For some reason, it's a javascript assignment (I have no idea how that's supposed to relate to data compression...). I've never worked with js before, or really done anything web related. After about an hour of messing around, I finished my shitty memory game. Check it out.

Also, Firebug is awesome.

Edit: I've only tested this in Firefox, so IE and Opera may explode, or something.

Driv3MeFar

Driv3MeFar

 

Blarg

Every now and then at work I can't help but get the feeling "Holy crap, I make video games!" Today was one of those days.

Since I'm a tools programmer, almost all my changes are exclusively PC side. Last week I started making my first console-side feature, and today I started testing it. Hitting F5 in Visual Studio and seeing the Xbox on the other side of the cubical boot up my test level made me feel like I was twelve years old. It was awesome.

In school related news, I've been working on my animation project lately. MSDN documentation for some of their model loading and skinned mesh stuff is terrible. Like completely not helpful. Things like the help page for ID3DXSkinInfo. If all I need to know is function return types and signatures, I'll check intellisense. If I want to know more than that, which as it turns out I do, I guess I'm just screwed [flaming].

Driv3MeFar

Driv3MeFar

 

Vegas, Baby!

So, here it is, the SUPER BIG ANNOUNCEMENT I'm sure you all been waiting for.

I've been working at Surreal since the beginning of summer, and we've finally announced the project I've been (and still am) a part of: This is Vegas. Now, since I'd rather like to keep my job, I can't really say anything about the game other than what we announce to the public, other that the fact that it is super awesome.

Also, if you feel so inclined, digg it [grin].

Alright, enough corporate shilling for me for now. Back to work!

Driv3MeFar

Driv3MeFar

 

Ramble On...

Since it looks like I'm going to spend the next hour compiling (damn you, full rebuilds!), I might as well update the journal. ISO work is suspended indefinitely, as I have quite a bit of school work and work work to do. The next two assignments that require my attention are a fairly boring JPEG codec, and a fairly interesting animation project. Even though I'm not working on ISO, I'll be finishing my render component and shader architecture with the animation project, plus working on a quaternion library with interpolation and all that fun stuff. I'm actually looking forward to it.

In other school related news, abstract algebra makes my head hurt. You've got groups, fields, rings, vector spaces, algebras (including Lie algebras, Jordan algebras, division algebras), and lots of other terminology for things that to me seem really similar other than an axiom or two that separate each of them. I have trouble remembering which one is which, and what axioms I need to prove for a set to be a field vs. a ring vs. an Abelian group vs. whatever. I love the class, I just wish I could keep track of all this stuff better.

Other than school and work, I've been playing lots of TF2 lately. I know other people here play, so I've been thinking about trying to organize a GDNet match. The only problem I could see is that we'd probably have lots of people from all different time zones wanting to play. Stupid time zones.

Driv3MeFar

Driv3MeFar

 

Unsexy ISO Update

I haven't made much progress on ISO. The new semester's started, so I've been busy with that and work. Depending on how long it takes me to do my Huffman Coding assignment due Wednesday, I'd like to make some progress on the project by the end of the week.

I think I'll take a break from finishing the component system and rendering queue to implement map serialization. The current plan is to save maps out to XML files (tinyXML is awesome), but I'll figure out the exact format and everything when I start working on it.

Anyway, the big news is that there's big news coming. Stay tuned next week for a big announcement. Word.

Also, dig the new journal footer. I need to learn CSS or something to make my journal look all spiffy-like.

PS: For those of you who have the new-ish Futurama DVD and didn't check the bonus features, there's a math lecture that goes into some of the math referenced on the show. It's may more entertaining than it sounds, check it out.

Driv3MeFar

Driv3MeFar

 

I Am Spoiled

qsort [] = []

qsort (x : xs) = qsort less ++ [x] ++ qsort greater
where
less = [n | n greater = [m | m x]


Haskell is neat.

On a semi-related note, I downloaded vim today for hacking around in Haskell (I've previously been using notepad [grin]). My first impression is that I miss Visual Studio.

I've always been a bit of a Microsoft-friendly developer. I've gone from MSVS 6.0 to 2003 to 2005, have always developed on Windows, and use Direct3D for my all my graphics needs (and Pix is the shit, as well). I don't know if I've just become spoiled from all this, but vim feels so...bare compared to a full-blown IDE like Visual Studio. It kind of scares me, really.

Driv3MeFar

Driv3MeFar

 

D'Oh

So, yesterday I "fixed" a bug in our editor at work, and got mad at the people who originally wrote the system for not testing an obviously broken code path.

Then, I checked in the fix to source control, and broke our game, because I hadn't tested an obviously broken code path in the fix.

I'm a jackass.

Driv3MeFar

Driv3MeFar

 

Christmas Loot

Haven't made much progress on ISO. Been mainly playing around with shaders now that they're working. I'm tooling around a bit because my next big task is finishing the render component system, then on to map serialization. I'm not too eager to start either of them at the moment.

Anyway, a few days ago was Christmas (in case you missed it [lol]), and this year was a good one for me. I got a copy of Programming in Haskell, and a long over due copy of Design Patterns, plus an amazon gift card that will be going towards Advanced Windows Debugging (as recommended by Raymond Chen).

Other than that, I got a PSP with Jeanne D'Arc and FF Tactics, a digital camera, the Simpsons Movie DVD, and lots of other goodies. I love Christmas [grin].

Driv3MeFar

Driv3MeFar

 

Programmable Pipeline Goodness

Okay, so it may not look like I've made any progress (in fact, based on screen shots alone, it would appear I've regressed since my last ISO update), but this picture in fact represents a big step forward.

You see, my tiles are now being drawn with the magic of pixel shaders! Yay! Now that I've got my shader system sort of working (more on that in a bit), I can start working on my component based system and render command queue, so this was a pretty important step towards where I want to go.

As for how my drawing now works, well, it's actually really simple. I hold all my effects in xml files, that look something like this:
testeffect.xml

"1.0" ?>
"test.fx">

g_mWorldViewProjection
g_Texture





This tells me the name of the .fx file, and all the variables needed for that shader. This information is all loaded in by my Effect class:
Effect.h

#ifndef EFFECT_H
#define EFFECT_H

#include
#include
#include
#include
#include "../../Main/Header Files/Serialize.h"

namespace ISO
{
namespace Graphics
{
class BaseShaderVariable
{
public:
virtual ~BaseShaderVariable() {}

virtual void Set(ID3DXEffect *d3dEffect) = 0;
};

template class T>
class ShaderVariable : public BaseShaderVariable
{
public:
ShaderVariable(std::string& _name = "", T _val = T()) : val(_val), name(_name) {}
T val;
std::string name;
virtual void Set(ID3DXEffect *d3dEffect)
{
d3dEffect->SetValue(name.c_str(), &val, sizeof(T));
}
};

//ShaderVariable Set specializations
void ShaderVariable::Set(ID3DXEffect *d3dEffect)
{
d3dEffect->SetMatrix(name.c_str(), &val);
}

void ShaderVariablefloat>::Set(ID3DXEffect *d3dEffect)
{
d3dEffect->SetFloat(name.c_str(), val);
}

void ShaderVariableint>::Set(ID3DXEffect *d3dEffect)
{
d3dEffect->SetInt(name.c_str(), val);
}

void ShaderVariable::Set(ID3DXEffect *d3dEffect)
{
d3dEffect->SetTexture(name.c_str(), val);
}

class Effect : public ISerializable
{
public:
Effect(LPDIRECT3DDEVICE9 _device);
~Effect();

bool Set();

LPD3DXEFFECT GetEffect();

stdext::hash_map m_ShaderVars;
protected:
//write (serialize) to file
virtual bool operatorconst std::string &filename);
//read (deserialize) from file
virtual bool operator>>(const std::string &filename);
private:
LPD3DXEFFECT m_D3dEffect;
LPDIRECT3DDEVICE9 m_Device;
};
}
}

#endif




Effect.cpp

#include "../Header Files/Effect.h"
//#include
//#include
#include
#include "../../tinyxml/tinyxml.h"

ISO::Graphics::Effect::Effect(LPDIRECT3DDEVICE9 _device)
: m_Device(_device)
{
}

ISO::Graphics::Effect::~Effect()
{
for (stdext::hash_map::iterator it = m_ShaderVars.begin(); it != m_ShaderVars.end(); ++it)
{
delete it->second;
}
}

bool ISO::Graphics::Effect::Set()
{
/*std::for_each(m_ShaderVars.begin(), m_ShaderVars.end(),
std::bind2nd(std::mem_fun(&ISO::Graphics::BaseShaderVariable::Set), m_D3dEffect) );*/
for (stdext::hash_map::iterator it = m_ShaderVars.begin(); it != m_ShaderVars.end(); ++it)
{
it->second->Set(m_D3dEffect);
}
return true;
}

LPD3DXEFFECT ISO::Graphics::Effect::GetEffect()
{
return m_D3dEffect;
}

/********************************************************************************************
* ISerializable Interface *
********************************************************************************************/
//write (serialize) to file
bool ISO::Graphics::Effect::operatorconst std::string &/*filename*/)
{
//@todo: implement this, maybe?
assert(0);
return false;
}

//read (deserialize) from file
bool ISO::Graphics::Effect::operator>>(const std::string &filename)
{
TiXmlDocument doc(filename);
if (!doc.LoadFile())
{
return false;
}

TiXmlHandle hDoc(&doc);
TiXmlHandle hRoot(NULL);

//name
TiXmlElement *pElem = hDoc.FirstChildElement().Element();
if (!pElem)
{
return false;
}
hRoot = TiXmlHandle(pElem);

//std::string name = "..\\Effects\\";
std::string name = pElem->Attribute("name");
HRESULT hr = D3DXCreateEffectFromFile(m_Device, name.c_str(), NULL, NULL, D3DXFX_NOT_CLONEABLE | D3DXSHADER_DEBUG | D3DXSHADER_NO_PRESHADER, NULL, &m_D3dEffect, NULL);
if (FAILED(hr))
{
return false;
}

TiXmlElement *VarNode = hRoot.FirstChild( "Variables" ).FirstChild().Element();
while (VarNode)
{
std::string varType = VarNode->ValueStr();
assert(VarNode->FirstChild());
std::string varName = VarNode->FirstChild()->ValueStr();
//test hackery
assert(m_ShaderVars.find(varName) == m_ShaderVars.end());
if (varType == "int")
{
m_ShaderVars[varName] = new ShaderVariableint>(varName);
}
else if (varType == "float")
{
m_ShaderVars[varName] = new ShaderVariablefloat>(varName);
}
else if (varType == "float2")
{
m_ShaderVars[varName] = new ShaderVariable(varName);
}
else if (varType == "float3")
{
m_ShaderVars[varName] = new ShaderVariable(varName);
}
else if (varType == "float4")
{
m_ShaderVars[varName] = new ShaderVariable(varName);
}
else if (varType == "matrix")
{
m_ShaderVars[varName] = new ShaderVariable(varName);
}
else if (varType == "texture")
{
m_ShaderVars[varName] = new ShaderVariable(varName, NULL);
}
else
{
assert(0);
//?
}
VarNode = VarNode->NextSiblingElement();
}
return true;
}




You can probably already see what I was trying to do. The renderer doesn't really need to know anything about the shader to get it working. Each component can have it's own related effect, and can muck about with the variables as needed. Then, it passes this off to the renderer, which simply Sets the shader, and draws whatever geometry is needed. It's still pretty WIP (for example, I think instead of storing variables by name I'm going to store them by semantic to make things more generic), but it works. Here's what the new render loop looks like:
Renderer.cpp

if( SUCCEEDED( m_D3DDevice->BeginScene() ) )
{
//m_D3DDevice->SetTransform(D3DTS_PROJECTION, &m_MatProj);
D3DXMATRIX view;
D3DXVECTOR3 eye = m_Cam.GetEyeVec();
D3DXVECTOR3 at = m_Cam.GetAtVec();
D3DXVECTOR3 up = m_Cam.GetUpVec();
D3DXMatrixLookAtLH(&view, &eye, &at, &up );
//m_D3DDevice->SetTransform(D3DTS_VIEW, &view);

D3DXMATRIX viewProj;
D3DXMatrixMultiply(&viewProj, &view, &m_MatProj);
ShaderVariable* matVar = dynamic_cast*>(m_Effect->m_ShaderVars["g_mWorldViewProjection"]);
assert(matVar);
matVar->val = viewProj;

m_D3DDevice->SetStreamSource(0, m_MasterGeometry->GetVertexBufferPointer(), 0, sizeof(MapVertex));
m_D3DDevice->SetIndices(m_MasterGeometry->GetIndexBufferPointer());
m_D3DDevice->SetFVF(m_MasterGeometry->GetFVF());
for (std::vector::iterator it = m_DrawInfos.begin(); it != m_DrawInfos.end(); ++it)
{

ShaderVariable* texVar = dynamic_cast*>(m_Effect->m_ShaderVars["g_Texture"]);
assert(texVar);
texVar->val = m_TextureBuffer[it->TextureIndex].GetTexturePointer();

m_Effect->Set();
HRESULT hr = m_Effect->GetEffect()->SetTechnique("RenderScene");
if (FAILED(hr))
{
_asm
{
int 3
}
}
//m_D3DDevice->SetTexture(0, m_TextureBuffer[it->TextureIndex].GetTexturePointer());
UINT numPasses = 0;
m_Effect->GetEffect()->Begin(&numPasses, 0);
for (UINT i = 0; i {
m_Effect->GetEffect()->BeginPass(i);
HRESULT hr = m_D3DDevice->DrawIndexedPrimitive( it->Type,
it->BaseVertexIndex,
it->MinIndex,
it->NumVertices,
it->StartIndex,
it->PrimitiveCount );
if (FAILED(hr))
{
assert(0);
}
m_Effect->GetEffect()->EndPass();
}
m_Effect->GetEffect()->End();
}

// End the scene
m_D3DDevice->EndScene();
}




An here are the stupid little test shaders I'm using:
test.fx

//--------------------------------------------------------------------------------------
// Global variables
//--------------------------------------------------------------------------------------
float4x4 g_mWorldViewProjection; // World * View * Projection matrix
texture g_Texture;

//--------------------------------------------------------------------------------------
// Texture samplers
//--------------------------------------------------------------------------------------
sampler TextureSampler =
sampler_state
{
Texture = ;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};

//--------------------------------------------------------------------------------------
// Vertex shader output structure
//--------------------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Position : POSITION; // vertex position
float2 TextureUV : TEXCOORD0; // vertex texture coords
};


//--------------------------------------------------------------------------------------
// This shader computes standard transform and lighting
//--------------------------------------------------------------------------------------
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION,
float3 vNormal : NORMAL,
float2 vTexCoord0 : TEXCOORD0 )
{
VS_OUTPUT Output;
// Transform the position from object space to homogeneous projection space
Output.Position = mul(vPos, g_mWorldViewProjection);

Output.TextureUV = vTexCoord0;
return Output;
}


//--------------------------------------------------------------------------------------
// Pixel shader output structure
//--------------------------------------------------------------------------------------
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // Pixel color
};


//--------------------------------------------------------------------------------------
// This shader outputs the pixel's color by modulating the texture's
// color with diffuse material color
//--------------------------------------------------------------------------------------
PS_OUTPUT RenderScenePS( VS_OUTPUT In )
{
PS_OUTPUT Output;

Output.RGBColor = tex2D(TextureSampler, In.TextureUV);

return Output;
}


//--------------------------------------------------------------------------------------
// Renders scene to render target
//--------------------------------------------------------------------------------------
technique RenderScene
{
pass P0
{
VertexShader = compile vs_2_0 RenderSceneVS();
PixelShader = compile ps_2_0 RenderScenePS(); // trivial pixel shader (could use FF instead if desired)
}
}


Driv3MeFar

Driv3MeFar

 

I Have Nothing Of Value To Say, So...

Last night a bunch of us at Surreal had a friendly TF2 match against PopCap (friendly because we had already beaten them in the game developers tournament we have going on [grin]), then met up afterwards and went out drinking. Sometimes living in Seattle is pretty fun.

Also, working with a mild hangover sucks.

Driv3MeFar

Driv3MeFar

 

Refactoring, Again

So, I haven't touched my rendering code in a month or so, which can only mean one thing. That's right, it's time to rewrite the whole thing [grin]!

It's hard to get any progress done on ISO whilst constantly rewriting my drawing code, but to be honest making a game out of it is kind of secondary. I started the project with mostly to tool around with DirectX, and so far that's pretty much what I've been doing.

So, quick review: right now, my architecture is pretty bare-bones. I have a monolithic Renderer class that holds all the game's geometry. Objects that reference that geometry, such as map tiles, simply hold indexes into the renderer's geometry buffers, and use those to tell the renderer what to draw and when to draw it. This was fine when all I was drawing was a bunch of tiles, but now it's getting annoying to have to add new buffers to the renderer for each new type of object I want to draw, and passing around buffer indices is, in my opinion, inelegant.

So, how am I going to fix this? I don't want a whole bunch of random objects holding on to vertex and index data in and of themselves. The solution is component based architecture. The new plan is to make a Renderable component that all drawable objects can contain. This component will hold all the geometry information, as well as things like world transforms, materials, and effects. When an object wants to draw, it's Renderable component will add a draw request to the renderer's render queue. The request will contain all necessary information that the renderer needs to draw the object (i.e., all the geometry and the like). This should all be pretty simple thanks to the handy event system I have in place.

I know talk is cheap, and none of this really means anything until I have my implementation done, but at least I know what I want to do now.

Here's what I'm going for, in pseudo-code:

Renderer
{
hash_map MasterGeometry;
queue DrawRequests;

struct DrawInfo
{
FVF;
Vert vOffset;
Index iOffset;
Effect *effect;
Material *material;
int numPrims;
};

RequestDraw(FVF fvf, GeometryBuffer buffer, Effect *fx, Material *mat, int prims)
{
if (!IsInMasterGeometry(buffer))
{
AddGeometry(buffer);
}
DrawInfo info(fvf);
info.vOffset = GetVertOffset(buffer);
info.iOffset = GetIndOffset(buffer);
info.material = mat;
info.effect = fx;
info.numPrims = prims;
queue.insert(info);
}

Draw()
{
for each DrawInfo info in DrawRequests;
DrawIndexedPrimitive(stuff from info);
}
}







So, the renderer still builds a list off all the geometry from the incoming draw requests, so I can batch things intelligently and minimize the actual draw calls. That's the plan at least, I'm sure there will be lots of complications and headaches to work out. This is the first time I've messed around with this sort of thing, so it should be an adventure. I'll get started on the implementation when I finish the dozen or so school projects that were due ...last week-ish.

Driv3MeFar

Driv3MeFar

 

Memory Leaks Are Bad, Mmmkay

So I've been very bad, and have been leaking memory in ISO. Yeah, yeah, tar and feather me for it later.

I set out to fix them tonight, but I'm pretty sure I'm going out of my fucking mind. All of my reported leaks are D3D handles, which is especially bad, but I can't for the life of me figure out why they're still getting reported.

For example, most of my leaks appear to be from my textures. I have my out wrapper around the D3D texture interface, which is as follows (this code has changed a bit in the past hour or two, so bear with all the commented out nonsense):
Texture.h

#ifndef TEXTURE_H
#define TEXTURE_H

#define D3D_DEBUG_INFO
#include
#include
#include
#include
#include "../../Main/Header Files/Resource.h"

namespace ISO
{
namespace Graphics
{
class Texture
{
public:
Texture(LPDIRECT3DDEVICE9 device = NULL, std::string filename = "");
/*Texture(const Texture& tex);
const Texture& operator=(const Texture& tex);*/
~Texture();

LPDIRECT3DTEXTURE9 GetTexturePointer() const;
const std::string& GetFileName() const;

ULONG Release();
private:
std::string m_Filename;
// Base::auto_resource m_Texture;
mutable LPDIRECT3DTEXTURE9 m_Texture;

};
}
}

#endif



Texture.cpp

#include
#include "../Header Files/Texture.h"

ISO::Graphics::Texture::Texture(LPDIRECT3DDEVICE9 device, std::string filename)
: m_Filename(filename),
m_Texture(NULL)
{
if (!device)
{
return;
}
LPDIRECT3DTEXTURE9 texture;
HRESULT hr = D3DXCreateTextureFromFile(device, filename.c_str(), &texture);
if (FAILED(hr))
{
return;
}
m_Texture = texture;
}

//ISO::Graphics::Texture::Texture(ISO::Graphics::Texture &tex)
//{
// //*m_Texture = *tex.GetTexturePointer();
//}
//ISO::Graphics::Texture::Texture(const Texture& tex)
//: m_Texture(tex.m_Texture)
//{
// tex.m_Texture = NULL;
//}
//
//const ISO::Graphics::Texture& ISO::Graphics::Texture::operator=(const ISO::Graphics::Texture& tex)
//{
// m_Texture = tex.m_Texture;
// tex.m_Texture = NULL;
// return *this;
//}

ISO::Graphics::Texture::~Texture()
{
/*if (m_Texture)
{
m_Texture->Release();
}*/
}

LPDIRECT3DTEXTURE9 ISO::Graphics::Texture::GetTexturePointer() const
{
return m_Texture;//.Get();
}

const std::string& ISO::Graphics::Texture::GetFileName() const
{
return m_Filename;
}

ULONG ISO::Graphics::Texture::Release()
{
assert(m_Texture);
return m_Texture->Release();
}



Simple enough, unless (as is highly possible) I'm missing something obvious. I keep a std::vector of these Textures in my renderer, which theoretically get released when the renderer is destroyed:
Renderer.cpp

ISO::Graphics::Renderer::~Renderer()
{
for (unsigned i = 0; i {
delete m_Tiles.Geometry;
}

for (unsigned i = 0; i {
delete m_UIBuffers.first;
}

for (unsigned i = 0; i {
//delete m_TextureBuffer;
m_TextureBuffer.Release();
}
//m_TextureBuffer.clear();

if (m_D3DDevice)
{
m_D3DDevice->Release();
//m_D3DDevice = NULL;
}

if (m_D3D)
{
m_D3D->Release();
//m_D3D = NULL;
}

delete m_Selector;
delete m_MasterGeometry;
}



I've set breakpoints in the Renderer destructor, and it is indeed getting called. Texture->Release is returning 0, so it doesn't appear that I have any dangling references out there. Why, then, do I still get a ton of leaks reported coming from my textures?

I am confused and tired. Little help [grin]?

Driv3MeFar

Driv3MeFar

 

Flocking, A Simple Overview

Last entry I put of a screen shot of my flocking demo for AI class. The algorithm isn't very complicated, but I figured I'd explain it a bit more and throw up some source code for you.

First off, you can download and play around with the demo here. The zip contains an xml file, txt file, and an .exe which I promise won't kill your computer. Click the mouse button to set the flock's target, 'z' and 'x' make the flock speed up and slow down, respectively. It's win32, so if you're using a Mac or *nix system, no luck for you. I'd say I was sorry about that, but I'm not [wink].

Right, so flocking. The general flocking algorithm was put forth by Craig Reynolds in 1986. Basically, your flock is made up of individual agents, called "boids". Each boid has several steering behaviors to try to keep it with the rest of the flock: separation, alignment, and cohesion. Basically, you want a boid to stay a certain distance from all the others (separation), go in the same direction as the rest of the flock (alignment), and try to stay with the flock in general (cohesion).

This is simple enough to code. Every update, get all nearby boids. Then you need to get three steering vectors for that update. For separation, look at the nearest boid to you. If it is too close, steer away from it. For alignment, simply steer in the same direction as the nearest boid. For cohesion, find the center of the visible flock and steer towards that. These three vectors are then combined with a weighted sum, and results in your new velocity.

That's the basic idea, and it's simple to expand upon. If you want to flock to a set goal, add a fourth steering behavior that moves the boid toward its goal every frame. For obstacle avoidance, add a steering behavior that will look ahead for collision and will avoid anything you may collide with in the near future.

Simple stuff, but you can get some neat emergent behavior out of just these few steering behaviors. Here's what my final boid code looked like (hint: it's not very good):
Boid.cpp

#include "../Header Files/Boid.h"
#include "../../Main/Header Files/Event.h"
#include "../../Main/Header Files/Input.h"
#include "../../Physics/Header Files/Object.h"
#include
#include
#include
#include
#include
#include

extern CS380::Base::EventManager *g_Events;
extern Mouse *g_mouse;
extern std::vector g_Circles;

CS380::AI::Boid::BoidContainer CS380::AI::Boid::AllBoids;
CS380::Math::CVector3 CS380::AI::Boid::PointOfInterest;

CS380::AI::Boid::Boid(CS380::Math::CVector3 *pos, CS380::Math::CVector3 *dir)
: m_angle(0),
m_radius(100),
m_position(*pos),
m_heading(*dir),
m_DrawBounds((int)(pos->x - 5), (int)(pos->y - 5), 10, 10),
m_DrawAngleStart(0.f),
m_DrawAngleSweep(45.f)
{
ReadWeights();
AllBoids.push_back(this);
g_Events->RegisterEventHandler("flock", this, &Boid::Flock);
g_Events->RegisterEventHandler("draw", this, &Boid::Draw);
}

CS380::AI::Boid::~Boid()
{
assert( std::find(AllBoids.begin(), AllBoids.end(), this) != AllBoids.end() );
AllBoids.erase( std::find(AllBoids.begin(), AllBoids.end(), this) );
}

void CS380::AI::Boid::Flock( const Base::Event* /*evt*/ )
{
UpdateVisibleBoids();
Math::CVector3 newAccel;
if ( !m_VisibleBoids.empty() )
{
Boid *closest = NULL;
float dist = m_radius;
for (BoidIter it = m_VisibleBoids.begin(); it != m_VisibleBoids.end(); ++it)
{
float d = ((*it)->m_position - m_position).Length();
if ( d {
dist = d;
closest = *it;
}
}

newAccel += GetSeparation(closest, dist);
newAccel += GetHeading(closest);
newAccel += GetCenter();
}
newAccel += GetForward();
newAccel += AvoidObsticles();

m_heading = (m_heading + newAccel);//.Norm();

if (m_heading.Length() > 1.f)
{
m_heading.Normalize();
}
bool inCollision = false;
for (std::vector::iterator it = g_Circles.begin(); it != g_Circles.end(); ++it)
{
if ( (*it)->Intersects(m_position + m_heading) )
{
inCollision = true;
break;
}
}
if (!inCollision)
{
m_position += m_heading;
}
m_DrawBounds = Gdiplus::Rect((int)(m_position.x - 5), (int)(m_position.y - 5), 10, 10);
}

void CS380::AI::Boid::Draw( const Base::Event* evt )
{
assert( evt->IsA(CS380::Base::EVT_1) );
const CS380::Base::Event1 *evt1
= dynamic_castconst CS380::Base::Event1 *>(evt);
assert( evt1 );

Math::CVector3 temp(m_heading);
if (temp.y 0.f)
{
temp.x = -temp.x;
}
float headingAngle = Math::RadToDeg( acosf( temp * Math::CVector3( 1, 0, 0 ) ) ) + (temp.y 0.f ? 0.f : 180.f);
m_DrawAngleStart = headingAngle - 25;


Gdiplus::SolidBrush sbrush(Gdiplus::Color::Red);
Gdiplus::Pen pen(&sbrush, 5.0);
Gdiplus::Status status = evt1->param1->DrawPie(&pen, m_DrawBounds, m_DrawAngleStart, m_DrawAngleSweep);
}

void CS380::AI::Boid::UpdateVisibleBoids()
{
m_VisibleBoids.clear();
for (BoidIter it = AllBoids.begin(); it != AllBoids.end(); ++it)
{
if (*it == this)
{
continue;
}

Math::CVector3 diff = (*it)->m_position - m_position;
if (diff.Length() > m_radius)
{
continue;
}
if (diff.Norm() * m_heading.Norm() {
continue;
}
m_VisibleBoids.push_back(*it);
}
}

CS380::Math::CVector3 CS380::AI::Boid::GetSeparation(Boid *closest, float dist)
{
assert( closest );
float ratio = dist / m_SeparationDist;
ratio = min( m_SeparationWeightMax, max(m_SeparationWeightMin, dist) );
if (dist == m_SeparationDist)
{
return Math::CVector3();
}
else
{
return (closest->m_position - m_position).Norm() * (dist > m_SeparationDist ? ratio : -ratio);
}
}

CS380::Math::CVector3 CS380::AI::Boid::GetHeading(Boid *closest)
{
assert( closest );

return closest->m_heading.Norm() * m_HeadingWeight;
}

CS380::Math::CVector3 CS380::AI::Boid::GetCenter()
{
Math::CVector3 center;
for (BoidIter it = m_VisibleBoids.begin(); it != m_VisibleBoids.end(); ++it)
{
center += (*it)->m_position;
}
center /= static_castfloat>(m_VisibleBoids.size());

return (center - m_position).Norm() * m_CenterWeight;
}

CS380::Math::CVector3 CS380::AI::Boid::GetForward()
{
Math::CVector3 forward = PointOfInterest - m_position;

float diff = (forward.Length() - m_ForwardWeightMin);
float urgency = fabs(diff);

urgency = min(m_ForwardWeightMax, max(m_ForwardWeightMin, urgency));

return forward.Norm() * ( urgency * (diff > 0 ? 1 : -1) );
}

CS380::Math::CVector3 CS380::AI::Boid::AvoidObsticles()
{
CS380::Physics::Circle *c = NULL;
float dist = m_CollisionDist;
//get nearest obsticle
for (std::vector::iterator it = g_Circles.begin(); it != g_Circles.end(); ++it)
{
float d = ((*it)->m_position - m_position).Length();
if ( d {
dist = d;
c = *it;
}
}

if (!c)
{
return Math::CVector3();
}

Math::CVector3 toCircle = c->m_position - m_position;

if (toCircle.Length() > m_CollisionDist)
{
return Math::CVector3();
}
toCircle.Normalize();

float dot = toCircle * m_heading.Norm();
if (dot 0.f)
{
return Math::CVector3();
}
dot = 1 - dot;
dot *= (m_CollisionDist - dist + c->m_radius) / m_CollisionDist;

float urgency = min(m_CollisionWeightMax, max(m_CollisionWeightMin, dot));

Math::CVector3 ret = (m_heading.Norm() - toCircle).Norm();

return ret * urgency;
}

void CS380::AI::Boid::ReadWeights()
{
std::ifstream file("flocking constants.txt");
std::string line;
std::getline(file, line);
std::stringstream ss(line);
ss >> m_SeparationDist; ss >> m_SeparationWeightMax; ss >> m_SeparationWeightMin;
std::getline(file, line);
std::stringstream ss2(line);
ss2 >> m_HeadingWeight;
std::getline(file, line);
std::stringstream ss3(line);
ss3 >> m_CenterWeight;
std::getline(file, line);
std::stringstream ss4(line);
ss4 >> m_ForwardWeightMax; ss4 >> m_ForwardWeightMin;
std::getline(file, line);
std::stringstream ss5(line);
ss5 >> m_CollisionDist; ss5 >> m_CollisionWeightMax; ss5 >> m_CollisionWeightMin;
}


Driv3MeFar

Driv3MeFar

 

Turkey Day!

Happy Thanksgiving to all my American readers! This year we're deep frying a turkey at my house to celebrate. Should be pretty delicious, provided we don't burn the house down. And we're drinking. Lots and lots of drinking.

In dev related news, not much has been going on. I have made a simple "selector" class for determining which tile is selected in the iso map editor, but I'm not too happy with it yet. So far, it's pretty tightly coupled to my renderer because it holds it's own geometry and transforms to tell the renderer how to draw it. I think I may implement some sort of render queue, so that individual objects can provide their own geometry buffers to the renderer for drawings sake, but it still needs a bit of work. Maybe tomorrow I'll have some ISO code to show, provided I don't get too drunk/full of turkey and pass out (don't hold you're breath).

I also had an AI assignment due today for one of my school classes. I wasn't sure quite what I wanted to do, so I took the easy road and implemented simple flocking with obstacle avoidance and goal seeking. I'll post the code tomorrow or the day after (it's not very good), but for now, here's a screen shot:


Also, not to complain or anything, but today marks the first day since I joined GDNet that my post count is higher than my rating. Kinda sad, but I'm a ratings whore like that [wink].

Driv3MeFar

Driv3MeFar

Sign in to follow this  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!