Jump to content

  • Log In with Google      Sign In   
  • Create Account

Hodgman

Member Since 14 Feb 2007
Offline Last Active Private

#5186681 Issues with linking Fmod Studio low level library into project

Posted by Hodgman on 13 October 2014 - 05:41 AM

Is fmod.hpp inside C:\Program Files (x86)\FMOD SoundSystem\FMOD Studio API Windows\api\lowlevel\inc ?




#5186651 A stable portable threading implementation

Posted by Hodgman on 13 October 2014 - 02:35 AM

In short, barring the use of Boost, what would be the most lightweight and simple solution?

1) std::thread

2) pthreads (standard almost everywhere, and a thin wrapper library on Windows)

3) just wrapping the OS-specific libraries yourself as needed.

 

I actually do #3 myself, because it's really not much code.




#5186594 Should i to compile using static library or shared library?

Posted by Hodgman on 12 October 2014 - 08:02 PM

There's no big performance difference either way.

 

I prefer to just compile everything as static, and link them into my game's EXE because I personally find it to be a lot simpler, especially on Windows where DLLs require you to manually mark everything as being DLL-exported/imported/etc...




#5186591 Why do unused game assets get stored in the game?

Posted by Hodgman on 12 October 2014 - 07:40 PM

Depending on the engine that you use, often you don't actually know which files are and aren't used when it comes time to ship their game...

 

Some engines have the workflow where:

1) Artist adds (pushes) asset to game's content folder / project.

2) Programmer writes code that uses this asset.

Sometimes, step 2 doesn't happen... or step 2 gets removed later, because making a game is a very experimental process. In those situations, when you've got 10's of thousands of assets, it's hard to notice that some of them aren't being used.

 

 

In some other engines, instead of there being a project that assets get added to, the engine instead pulls assets into the game data files as required. The engine looks at the code that's written, and generates a list of file-names that could possibly be loaded. From there, it uses a system like make to build those files from source assets (which are pushed into an intermediate repository by artists). Then only this list of files get added to the final, shipping project.

However, even in this system, it's possible to have unreachable code in the project, which still adds unused file-names to the build process, which causes unneeded source files to get dragged in, compiled and shipped...




#5186542 Terrain lighting artifacts

Posted by Hodgman on 12 October 2014 - 02:34 PM

If the interpolation is the case then every mesh you render would have same artifacts.

Every mesh does have the same artefacts. This isn't something specific to untextured low-poly terrain grids, but it just happens to be obvious in that case.


#5186350 Keyboard input: poll, push or pull?

Posted by Hodgman on 11 October 2014 - 06:56 AM

This might be relevant: http://www.gamedev.net/page/resources/_/technical/game-programming/designing-a-robust-input-handling-system-for-games-r2975




#5186346 Find distinct colors in texture, using a Compute Shader

Posted by Hodgman on 11 October 2014 - 06:01 AM

Yeah, create a buffer of int32's, then make an unordered access view of that buffer so that you can bind it to your compute shader.
If multiple threads are sharing the same buffer though, you'll have to use an atomic operation to OR in the bits.


#5186339 Deciding on a Game Engine?

Posted by Hodgman on 11 October 2014 - 05:07 AM

A few small things -
CryEngine is a $10/month subscription.
UE4 is $19/month, but you can cancel any time *and continue to use it*, and subscribe for another month/$19 later when you want to get new updates.
Unity also has a subscription option but it's something like $75/month IIRC...


#5185966 Portability: am I guaranteed for union size/base address to be the same on va...

Posted by Hodgman on 09 October 2014 - 06:19 AM

AFAIK, the behaviour of that is all implementation defined. The standard encourages you to avoid it... but the compiler vendors encourage it as the preferred way of breaking the aliasing rules...
As above, use int32_t / uint32_t from stdint.

On every Windows/Mac/Linux compiler I know of (for x86/x64), this hack should work -- as in all the structs will have the same address, all the array elements will be 32bit (if using the int types above), and there will be no padding in the arrays.

You could use some static_asserts to document your assumptions and to be notified in the future if a compiler doesn't agree with you wink.png




#5185965 porting procedural c++ to oo syntax

Posted by Hodgman on 09 October 2014 - 06:03 AM

as a design exercise, i've decided to try porting the procedural c++ code for my new collision maps API to oo syntax.
 
the procedural code is an array of structs, routines that operate on a single struct, and routines that operate on the array:

Here's a quick effort at rewriting it to be a bit more 'OO', turning some globals into arguments and killing off pages of code duplication:
namespace CollisionValue
{
  enum Type
  {
    Tree = 1,
    Cave = 2,
    Rock = 3,
    FruitTree = 4,
  }
}

struct ItemMapDescriptor
{
  struct Item
  {
    int x, z, radius;
  };
  int numItems;
  Item* items;
  int size;
  CollisionValue::Type type;
  int rarity;
  bool checkForWaterCollision;
  bool checkForTerrainCollision;
  bool checkForStorepitCollision;
};

class World
{
...
public:
  bool water_in_way(int mx, int mz, int x, int z);
  bool storepit_in_way(int mx, int mz, int x, int z);
};

class CollisionMap
{
  const static int s_size = 300;
  const static int s_cacheSize = 20;
  
  struct Cache
  {
    bool active;
    int age;
    int mx, mz;
    int cx, cz;
    CollisionValue::Type data[s_size][s_size];
  };
   
  Cache m_map[s_cacheSize];
  std::bitset<s_cacheSize> m_active;
  
public:
  CollisionMap()
  {
    memset(&m_map[0], 0, sizeof(Cache)*s_cacheSize);
    m_active.reset();
  }
  
  void clear_cache(int cache_index)
  {
    memset(&m_map[cache_index], 0, sizeof(Cache));
    m_active.reset(cache_index);
  }
  
  void clear_cache()
  {
    for (int a=0; a!=s_cacheSize; ++a)
      clear_cache(a);
  }
  
  void activate( int cache_index, int mx, int mz, int cx, int cz);
  {
    Cache& map = m_map[cache_index];
    assert( m_active.test(cache_index) == false );
    map.mx=mx;
    map.mz=mz;
    map.cx=cx;
    map.cz=cz;
  }

  void add(int cache_index, int x, int z, int rad, CollisionValue::Type value)
  {
    assert( cache_index < s_cacheSize && m_active.test(cache_index) == true );
    Cache& map = m_map[cache_index];
    int x1=x-rad, x2=x+rad;
    int z1=z-rad, z2=z+rad;
    x1 = max(0,x1);
    z1 = max(0,z1);
    x2 = min(s_size-1,x2);
    z2 = min(s_size-1,z2);
    for(int z=z1; z<=z2; ++z)
    {
      for(int x=x1; x<=x2; ++x)
          map.data[x][z] = value;
    }
  }
  
  void add_objectmap(int cache_index, int mx, int mz, int px, int pz, const ItemMapDescriptor& itemMap, const World& w)
  {
    assert( cache_index < s_cacheSize && m_active.test(cache_index) == true );
    Cache& map = m_map[cache_index];
    //  x,z    location of the plant in the map sq
    //  cx,xz   is the index of the collsion map the object is in
    //  x2,z2 is the location of the object in the collision map
    //  for each object in map, if within CM, add to CM
    for( int a=0; a!=itemMap.numItems; ++a )
    {
      //  skip every other rock
      if (a%itemMap.rarity != 0)
        continue;
      //  calc item's x z
      int x = px*itemMap.size + itemMap.items[a].x;
      int z = pz*itemMap.size + itemMap.items[a].z;
      //  calc CM index cx,cz
      //  if its not in this CM, skip the item
      int cx = x/s_size;
      int cz = z/s_size;
      if( cx != map.cx ||
          cz != map.cz )
        continue;
      //  if violates collision rules for this object type, skip the item
      if( (itemMap.checkForWaterCollision    && w.water_in_way(   mx,mz,x,z)) ||
          (itemMap.checkForStorepitCollision && w.storepit_in_way(mx,mz,x,z)) ||
          (itemMap.checkForTerrainCollision  && w.terrain_in_way( mx,mz,x,z)) )
        continue;
      //  ok, item is in CM index cx,cz
      //  add it to the CM
      //  calc x,z coords in the CM (x2,z2)
      int x2 = x - cx*s_size;
      int z2 = z - cz*s_size;
      int radius = itemMap.items[a].radius;
      add_(cache_index, x2, z2, radius, itemMap.type);
    }
  }
  
  void add_objects(int cache_index, int mx, int mz, int cx, int cz, const ItemMapDescriptor& itemMap, const World& w)
  {
    assert( cache_index < s_cacheSize && m_active.test(cache_index) == true );
    int px1,pz1,px2,pz2,px3,pz3;
    //  px1 etc are begin and end of loops for object maps
    int px1 =  cx   *s_size/itemMap.size;
    int px2 = (cx+1)*s_size/itemMap.size;
    int pz1 =  cz   *s_size/itemMap.size;
    int pz2 = (cz+1)*s_size/itemMap.size;
    for( int pz=pz1; pz<=pz2; ++pz )
    {
      for( int px=px1; px<=px2; ++px )
      {
        add_objectmap(cache_index, mx, mz, px, pz, itemMap, w);
      }
    }
  }
  
  void age_all_except(int cache_index)
  {
    for( int a=0; a!=s_cacheSize; ++a )
    {
      Cache& map = m_map[a];
      if(!m_active.test(a))
        continue;
      if( a == cache_index )
        map.age=0;
      else
        map.age++;
    }
  }
  

  typedef void (*CollisionGeneratorFnPtr)(CollisionMap&, int, int, int, int, int, const World&);
  
  int get_cache_index(int mx, int mz, int cx, int cz, CollisionGeneratorFnPtr fnGenerate, const World& w)
  {
    //  first: see if it exists, if so return its index...
    for(int a=0; a!=s_cacheSize; ++a)
    {
      if( !m_active.test(a) ||
          m_map[a].mx != mx ||
          m_map[a].mz != mz ||
          m_map[a].cx != cx ||
          m_map[a].cz != cz )
        continue;
      return a;
    }
    //  doesnt exist. need to make one.
    //  serch for inactive, if found, use it to generate a CM and return its index...
    for(int a=0; a!=s_cacheSize; ++a)
    {
      if (!m_active.test(a))
      {
        fnGenerate(*this, a, mx, mz, cx, cz, w);
        return a;
      }
    }
    //  didnt find inactive CM. must use LRU one.
    //  find LRU CM, use it to gen new CM, and return its index...
    int best=0;
    int bestval=0;
    for(int a=0; a!=s_cacheSize; ++a)
    {
      if (m_map[a].age > bestval)
      {
        best = a;
        bestval = m_map[a].age;
      }
    }
    fnGenerate(*this, best, mx, mz, cx, cz, w);
    return best;
  }
}

 
void gen_CM( CollisionMap& cm, int cache_index, int mx, int mz, int cx, int cz, const World& w )
{
  //  clear the collision map
  cm.clear_cache(cache_index);
  //  helps if you make it active, etc! also - cx,cz must be set to add plantmaps (trees, rocks, fruit trees)!
  cm.activate(cache_index, mx, mz, cx, cz);
  //  add trees as required...
  switch (g_map[mx][mz].coverage)
  {
    case WOODS:
    case JUNGLE:
      cm.add_objects(cache_index, mx, mz, cx, cz, g_jungleTreeDesc, w);
      break;
    case SAVANNA:
      cm.add_objects(cache_index, mx, mz, cx, cz, g_savannaTreeDesc, w);
      break;
  }
  cm.add_objects(cache_index, mx, mz, cx, cz, g_caveDesc, w);
  if (g_map[mx][mz].rocks)
    cm.add_objects(cache_index, mx, mz, cx, cz, g_rockDesc, w);
  if (g_map[mx][mz].fruittree)
    cm.add_objects(cache_index, mx, mz, cx, cz, g_fruitTreeDesc, w);
  if (g_map[mx][mz].cavern)
    cm.add_objects(cache_index, mx, mz, cx, cz, g_cavernDesc, w);
  cm.add_objects(cache_index, mx, mz, cx, cz, g_rocksheltersDesc, w);
  if (map[mx][mz].volcano)
    cm.add_objects(cache_index, mx, mz, cx, cz, g_volcanoDesc, w);
  add_volcano_to_CM(i,cx,cz);
  cm.add_objects(cache_index, mx, mz, cx, cz, g_hutsDesc, w);
  cm.add_objects(cache_index, mx, mz, cx, cz, g_permsheltersDesc, w);
  cm.add_objects(cache_index, mx, mz, cx, cz, g_tempshelters_Desc, w);
}//todo - fix all those globals^
 
 
.......
CollisionMap cm;
int mx=0, mz=0, cx=0, cz=0;
int idx = cm.get_cache_index(mx, mz, cx, cz, gen_CM, g_world);
That code could be a lot simpler (and more readable) if you took just a bit more care when writing it.


#5185694 [Solved] Lua "require" other files when loaded from memory

Posted by Hodgman on 08 October 2014 - 01:13 AM

It's probably not the most useful thing for you to look at (it's not meant to be tutorial code!), but my code to replace the package loader is here:
https://code.google.com/p/eight/source/browse/src/lua/lua.cpp
 
As long as your custom file system (packed asset system) can load a required lua file on demand and end up calling luaL_loadbuffer(L, bytes, numBytes, filename); then you should be ok.

I will take a closer look tommorow, but do you by chance know if its required to be loaded into the same lua_State?

I don't understand. When one lua_State requires you to load some code, why would you load it into a different lua_State?




#5185660 timestep with ET cap

Posted by Hodgman on 07 October 2014 - 07:11 PM

1. Yes (see 6)
2. Yes. Assuming you're using et in your physics calsulations of course smile.png
However, things may be simulated differently on PCs with different et values. E.g. When using Euler's method of numerical integration (the standard way of adding velocity*et to position per frame, etc) a person with a higher framerate can jump further than someone with a lower framerate,
Due to their gravity/momentum curves being integrated with different accuracies.
If that's an issue, then use a fixed timestep.
3. Only if you've requested vsync to be enabled AND the OS/driver has respected your request (which it may not - e.g. When in windowed mode on WinXP)
4. Yes, this is the standard way I know I to avoid the spiral of death
5. a framerate limiter stops the et from being too small. You might want one of those as well if you're seeing et values so small that they begin to cause issues with your physics/etc (e.g. dividing by zero at some point...)
6. Yes and no. Yes for time as it passes in the game world. No for time passing in the real world. A 33ms time step could still take any amount of real-world time to process.
7. Kind of... In cases where the game is performing at sub-realtime speeds, instead of becoming unresponsive/unplayable, it will instead play in slow motion... The image on screen might still only change (and the inputs might only be polled) at any rate, such as once a second if the simulation code is performing poorly.




#5185657 How to prepare myself to get a job in a AAA game company?

Posted by Hodgman on 07 October 2014 - 06:56 PM

Candidly, game development is not a 40 hour a week job and there's a lot of people willing to put the hours in out there. Some companies might pay lip service to "work life balance" but we all know it's not true.

I would say that more studios are figuring out that overtime is harmful to a project. When I worked at a 400 staff developer, we never did overtime (38 hours a week, every week). Recently at a <50 staff developer, when the management asked people to start doing 50hr weeks instead of 38hrs, the lead programmer resigned on the spot, followed by a lot of other senior staff in the following weeks. The backlash almost killed the studio.
When working for larger companies, you really do have a choice as to whether you'll put up with that sort of abuse or not. Just say no, kids!

However, yeah, as a beginner trying to get their foot in the door, you have no leverage at all and aren't in demand, so you'll probably want to choose to put up with these kinds of companies just to get the experience...


#5185638 Lines of code language comparisons

Posted by Hodgman on 07 October 2014 - 04:33 PM

But it can't possibly be a completely ignorable factor, seeing as more code clearly means more time spent coding

No, that isn't clearly true.
How is that not clearly true?
More code equals more time typing.

If I'm regurgitating code flat-out, I could probably write 10000 LOC a day... Which means that Braid is just two weeks work, right?
Obviously, that's not how things work. Jon Blow did not just sit down and churn out 100k loc non-stop and then have a game at the end of it.

Your average professional programmer is more likely to produce about 100 LOC per day, which indicates that raw typing is only about 1% of their actual job.
In any case of trying to optimize a process, you don't go after the parts of it that only take up 1% of the time first.

For any particular task, a productive language doesn't necessarily result in less lines, it results in less careful focused thinking required to write and read the code.
The majority of a programmer's job is actually reading other people's code, and then modifying code. Writing new code from scratch comes way after those two activities.

For an extreme example, compare one line of C# LINQ to the equivalent x86 Assembly.
In the former, you can have one very readable line that sums all the numbers in a linked list. In the latter, you've got pages of what may as well be hieroglyphics. Even someone who's an expert at assembly will take minutes to pour over those hieroglyphics and piece together their purpose / meaning. If you then had to modify the ASM, it would be a complex task requiring expert skills and a lot of time.
It would be much more productive if you could instead just modify some high level C# code.

To avoid jumping to conclusions here though, let's say you've got another task that requires carefully specifying the byte-by-byte memory layout of a complex, compressed data-structure of some kind. You actually care about the exact placement o your data in RAM/address-space here.
For this task, Lua is just out of the question (doesn't offer that capability, without modding the Lua VM at least). C# has the capability, but the code becomes extremely verbose and ugly. C has the capability by default and the code is simpler.
Jon Blow's game-state replay system is a good example of one of these complex "low-level" tasks, where a lower-level language like C ends up being a better fit than a higher level one like C#.

In both those examples, it's not the LOC count that makes a difference, it's -
• How readable is it? How long does it take someone to understand the workings of the algorithm embodied in the code?
• How fragile is it? How likely is it that a bug will be introduced when someone modifies the code (probably due to them failing to read an important detail)?
• How flexible is it? Can you edit the algorithm later without having to rewrite everything from scratch?
• How correct is it? Can a peer review prove formal correctness? Are there assertions for every assumption made by the coder, to detect any bugs that may occur later? If it is identified as incorrect/buggy later, how hard will it be to diagnose potential issues?


#5185197 Texture Storage + Texture Views AND mipmapping

Posted by Hodgman on 05 October 2014 - 04:57 PM

They both look mipmapped to me... except that in the 'texstorage' screenshot, it looks like the mipmaps have been generated using a worse algorithm than in the other screenshots.

 

Do you generate the mipmaps yourself, or do you just ask the GL driver to create them for you?

[edit]To answer my own question -- you're using glGenerateMipmap in your code snippet, so the driver is creating them for you biggrin.png

In that case, it just looks like that your drivers suck at creating mipmaps in some cases?

 

There's no special mipmap generation hardware in modern GPUs -- when you ask the driver to create them for you, you're actually just asking it to load up a built in shader program and dispatch a bunch of compute tasks, or draw a bunch of quads, filling in your mips with appropriate data.

IMHO, this is a really bad idea because every driver might use a different algorithm, landing you in situations like this... I'd prefer to just ship your own compute/fragment shaders for generating mips, or even better, to precompute them ahead of time using a very high quality algorithm and load them from disk.






PARTNERS