Jump to content

  • Log In with Google      Sign In   
  • Create Account

Hodgman

Member Since 14 Feb 2007
Offline Last Active Today, 08:39 AM

Posts I've Made

In Topic: What is faster cbuffer or data textures for MVP matrices?

Yesterday, 10:09 PM

In D3D11, you don't have to use VTF; you can use a regular Buffer object (like for vertices) and bind it to a texture slot (tbuffer type in the HLSL code).

If using instancing, you can't use multiple cbuffers (as all resources have to be the same for every instance), so tbuffers are the obvious choice.
If not using instancing, the overhead of updating a cbuffers probably dwarfs the cost of copying 2 to 48 bytes, so I don't imagine that updating cbuffers with indices instead of matrices will be a useful optimization.

With instancing, you could also put the matrix array in a cbuffer rather than a tbuffer, but I would guess this will be non-optimal.
Cbuffers are optimised assuming that every pixel/vertex will require every bit of data in the cbuffer, and older cards may not support array indexing (= may implement it as a huge nested if/elseif chain...).
Tbuffers and textures are optimised assuming that different vertices/pixels will need different subsets of the data, but that there may be some spatial locality that a cache would help with. They're implemented using the fetch hardware, so you know that array indexing will work, but also that it will be performing an actual (cached) memory request (whereas perhaps cbuffer data may have been pre-fetched into registers - wasting a huge number of them).

Lastly, you can put the matrix data into a vertex buffer and bind it to an Input Assembler slot, where the vertex layout / vertex declaration is responsible for defining how it is fetched for the vertex shader.
In D3D9, this is probably the best approach, as VTF was either slow and limited, or entirely unsupported back then. In D3D11, it's probably faster to define your own tbuffer and do the fetch yourself using SV_InsanceID.

In Topic: Occlusion Culling - Combine OctTree with CHC algorithm?

17 December 2014 - 07:11 PM

What would be some good alternatives, preferably with being able to keep the OctTree, for dynamic scenes?

That's a good question!  I just wanted to point out the inevitable GPU sync points involved in CPU-read-back techniques.

 

Obviously the best performance will be with precomputed techniques and static scenes...

 

For dynamic scenes, many engines are using depth occlusion queries, but entirely on the CPU-side to avoid the GPU-sync issues.

This generally requires your artists to generate very low-poly LOD's of your environment / occluders, and ensure that these LODs are entirely contained within the high-poly visual versions. You then rasterize them at low resolution on the CPU to a floating point depth buffer. To test objects for visibility, you find their 2D bounding box (or bounding ellipse) and test all the floats in that region to see if the bounding volume is covered or not.

 

At the moment, I'm using a 1bpp software coverage rasterizer, as mentioned here.

 

Another technique that I've seen used in AAA games is simple "occlusion planes". The level designers manually place large rectangles around the levels (usually inside walls of buildings / etc), which represent occluders. The game then frustum culls these rectangles and then sorts them by screen-space size and selects the biggest N number of them. Those N rectangles are then extruded into occluder frustums, and every visible object is tested to see if it's entirely inside any of those occluder frustums.

e.g.

visibleObjects = allObjects.Where( x => EntirelyOutsideFrustum(camera, x) == false );
visibleOccluders = allOccluders.Where( x => EntirelyOutsideFrustum(camera, x) == false );
bestOccluders = visibleOccluders.Sort( x => x.ScreenArea() ).Trim(0,10);
occluderFrustra = bestOccluders.Select( x => x.ExtrudeQuadToFrustum(cameraPos, x) );
reallyVisibleObjects = visibleObjects.Where( x => EntirelyInsideFrustum(occluderFrustra.ForEach(), x) == false );

You'd be suprised at how fast modern CPUs can burn through these kind of repeated, simple, brute-force checks... Sometimes simple flat lists will even out-perform complex structures like trees, due to how ridiculously slow random memory access patterns are compared to predictable linear patterns.

 

Other games use a combination of portals and occlusion planes.


In Topic: How much do I pay someone for coming up with some ideas for my game?

16 December 2014 - 09:48 PM

Add up how many hours both you and him have put into it and show those numbers to him.

Either agree to a royalty share based on that, or a fixed price, e.g. $20 x his hours.
In either case you need a lawyer to draft the agreement.

If negotiations fail, you're not legally obliged to pay him anything.... Game ideas aren't really subject to copyright - implementations of ideas are.

In Topic: declare custom types of ints?

16 December 2014 - 09:06 PM

yes, but if both parameters are of type eiTYPEDEF_INT, the compiler won't catch it if they are accidentally reversed will it? IE if i accidentally passed ani # as model #, and model # as ani #.

The whole point of those macros is to solve that problem for you - otherwise you'd just use the regular typedef keyword.
If you use:
eiTYPEDEF_INT(AnimID);
eiTYPEDEF_INT(ModelID);
Then passing an AnimID as a ModelID will result in a compiler error, saying can't convert AnimID to ModelID.
 
[edit]
The trick is that you end up with two completely different instatiations of the PrimitiveWrap template -- one using 'tag_ModelID' as an argument and one using 'tag_AnimID' as an argument. Those 'tag' types are just dummy structures with no use at all, except to trick C++ into cloning the PrimitiveWrap template into a new unique type. 
struct tag_ModelID;//useless structs, just to create a unique type
struct tag_AnimID;

typedef PrimitiveWrap<int, tag_ModelID> ModelID;//the useless structs are used as a template argument
typedef PrimitiveWrap<int, tag_AnimID>  AnimID; // so that the two resulting types are different

void PlayAnimation(ModelID some_model, AnimID some_ani);
...
AnimID a = AnimID(2);
ModelID m = ModelID(1);
PlayAnimation(m, a);//OK!
PlayAnimation(a, m);//error - can't convert arg#1 from PrimitiveWrap<int,tag_AnimID> to PrimitiveWrap<int,tag_ModelID>
PlayAnimation(1, 2);//error - can't implicitly convert arg#1 from int to PrimitiveWrap<int,tag_ModelID>
PlayAnimation(AnimID(1), ModelID(2));//error - can't convert arg#1 from PrimitiveWrap<int,tag_AnimID> to PrimitiveWrap<int,tag_ModelID>
PlayAnimation(ModelID(1), AnimID(2));//OK!

int id = m;//OK -- n.b. you CAN convert from ID's back into integers implicitly
ModelID m2 = id;//ERROR -- but you can't implicitly convert integers into IDs!
ModelID m3 = ModelId(id);//OK -- you have to explicitly convert them like this

In Topic: declare custom types of ints?

16 December 2014 - 05:54 PM

I use this code (and helper macros) to declare custom type-safe integer and pointer types.
 
template<class T,class Name>
struct PrimitiveWrap
{
	PrimitiveWrap() {}
	explicit PrimitiveWrap( T v ) : value(v) {}
	operator const T&() const { return value; }
	operator       T&()       { return value; }
private:
	T value;
};

# define eiTYPEDEF_INT( name )					\
	struct tag_##name;					\
	typedef PrimitiveWrap<int,tag_##name> name;		//

# define eiTYPEDEF_PTR( name )					\
	struct tag_##name;					\
	typedef tag_##name* name;				//
e.g.
eiTYPEDEF_INT( ModelId ); // ModelId is a typedef for int
eiTYPEDEF_PTR( AnimationId ); // AnimationId is a typedef for void*

void Play( ModelId m, AnimationId a )
{
  int modelId = (int)m;
  void* animPtr = (void*)a;
  ...
}

Animation anim;
AnimationId a = AnimationId(&anim);
ModelId m = ModelId(42);
Play(m, a);
The final asm should be the same as if you were using ints/void*'s, but you get to use C++'s compile-time type-safety system.

PARTNERS