I'm sure you can find useful help/tips in Jason's Game Engine Architecture book. It's a good way to begin and even master.
Game engine architecture
While it is true that below the level of a “mesh” there are no common or official terms, a single mesh can definitely have more than 1 material and render call."A mesh represents a geometry or model with a single material."
I outlined this on my site, where I referred to the sub-mesh level as “render part” for lack of a better term at the time (since then I have found “sub-mesh” to be better).
In such a simple case as this you could possibly draw the red and green parts in one draw call (though it would be non-trivial in any generic sense), but the fact is that the red and green areas are 2 different materials on the same mesh and it is meant to illustrate that in a more advanced case you will have no choice but to draw the red, switch materials and shaders, and then draw the green.
If Assimp says that the red, green, and blue are 3 different meshes, it’s wrong.
If it says there are 2 meshes but a mesh can have only 1 material, it’s wrong.
I have never used Assimp and don’t know its limitations, but if it says a mesh can have only 1 material then that sounds like a pretty bad limitation.
Yes, but that’s the active way.would this be the way:
A better way is the last-minute way.
Basic Graphics Implementation
L. Spiro
I read your site and liked it, however I couldn't fully understand how to implement what you call the last-minute way. For example, I don't understand what CStd::Min( LSG_MAX_TEXTURE_UNITS, CFndBase::m_mMetrics.ui32MaxTexSlot ); does.
Am I right in understanding you have two arrays : an array of last active states (for example m_psrvLastActiveTextures ) and the ones that are active at the time of the render call (for example m_psrvActiveTextures)?
And then you set the last active to the current one if they are not already equal?
I am still fuzzy on the details, though, it seems similar to the active way because you are swapping a current sate and a last one? Whats the difference?
Thanks!
The API may support 128 texture slots (CFndBase::m_mMetrics.ui32MaxTexSlot), but the engine only supports 32 (LSG_MAX_TEXTURE_UNITS).For example, I don't understand what CStd::Min( LSG_MAX_TEXTURE_UNITS, CFndBase::m_mMetrics.ui32MaxTexSlot ); does.
Not directly.Am I right in understanding you have two arrays
I have a structure which encapsulates all state information.
/** The render-state copy that allows us to know which states have changed since the last system update. */
struct LSG_RENDER_STATE {
/** Shader resources. */
LSG_SHADER_RESOURCE * psrShaderResources[LSG_MAX_TEXTURE_UNITS];
/** Sampler states. */
LSG_SAMPLER_STATE * pssSamplers[LSG_MAX_SHADER_SAMPLERS];
/** Render targets. */
LSG_RENDER_TARGET * prtRenderTargets[LSG_MAX_RENDER_TARGET_COUNT];
/** Input layouts. */
const LSG_INPUT_LAYOUT * pilInputLayout;
/** Vertex buffers. */
LSG_VERTEX_BUFFER pvbVertexBuffers[LSG_MAX_VERTEX_STREAMS];
/** Depth/stencil buffer. */
LSG_DEPTH_STENCIL * pdsDepthStencil;
/** Rasterizer state. */
LSG_RASTERIZER_STATE * prsRasterState;
/** Blend state. */
LSG_BLEND_STATE * pbsBlendState;
/** Depth/stencil state. */
LSG_DEPTH_STENCIL_STATE * pdssDepthStencilState;
/** Viewports. */
LSG_VIEWPORT vViewports[LSG_MAX_VIEWPORTS_SCISSORS];
/** Scissors. */
LSG_RECT rScissors[LSG_MAX_VIEWPORTS_SCISSORS];
/** Blend factors. */
LSGREAL fBlendFactors[4];
/** Clear color. */
LSGREAL fClearColor[4];
/** Clear depth. */
LSGREAL fClearDepth;
/** Stencil reference value. */
LSUINT32 ui32StencilRef;
/** 32-bit sample coverage for the output-merger state. */
LSUINT32 ui32SampleMask;
/** Total active vertex buffers. */
LSUINT32 ui32TotalVertexBuffers;
/** Stencil clear value. */
LSUINT8 ui8ClearStencil;
/** Total viewports/scissors. */
LSUINT8 ui8TotalViewportsAndScissors;
/** Total render targets. */
LSUINT8 ui8TotalRenderTargets;
};
Since my engine is cross-platform, LSG_SHADER_RESOURCE, LSG_SAMPLER_STATE, etc., are all typedefs.LSGDirectX11.h:
/** Sampler description. */
typedef D3D11_SAMPLER_DESC LSG_SAMPLER_DESC;
/** Rasterizer description. */
typedef D3D11_RASTERIZER_DESC LSG_RASTERIZER_DESC;
/** Render-target blend description. */
typedef D3D11_RENDER_TARGET_BLEND_DESC LSG_RENDER_TARGET_BLEND_DESC;
/** Blend description. */
typedef D3D11_BLEND_DESC LSG_BLEND_DESC;
/** Depth/stencil operation description. */
typedef D3D11_DEPTH_STENCILOP_DESC LSG_DEPTH_STENCILOP_DESC;
/** Depth/stencil description. */
typedef D3D11_DEPTH_STENCIL_DESC LSG_DEPTH_STENCIL_DESC;
/** The 1D texture type. */
typedef ID3D11Texture1D LSG_TEXTURE_1D;
/** The 2D texture type. */
typedef ID3D11Texture2D LSG_TEXTURE_2D;
/** The 3D texture type. */
typedef ID3D11Texture3D LSG_TEXTURE_3D;
/** The sampler type. */
typedef ID3D11SamplerState LSG_SAMPLER_STATE;
/** The shader resource view type. */
typedef ID3D11ShaderResourceView LSG_SHADER_RESOURCE;
/** The render target type. */
typedef ID3D11RenderTargetView LSG_RENDER_TARGET;
/** The depth/stencil type. */
typedef ID3D11DepthStencilView LSG_DEPTH_STENCIL;
/** The rasterizer state type. */
typedef ID3D11RasterizerState LSG_RASTERIZER_STATE;
/** The blend state type. */
typedef ID3D11BlendState LSG_BLEND_STATE;
/** The depth/stencil state. */
typedef ID3D11DepthStencilState LSG_DEPTH_STENCIL_STATE;
/** The viewport type. */
typedef D3D11_VIEWPORT LSG_VIEWPORT;
/** The scissor rectangle type. */
typedef D3D11_RECT LSG_RECT;
/** The input layout type. */
typedef ID3D11InputLayout LSG_INPUT_LAYOUT;
/** The vertex buffer type. */
typedef struct LSG_VERTEX_BUFFER {
const CVertexBuffer * pvbVertBuffer; /**< The vertex buffer pointer. */
LSUINT32 ui32Offset; /**< Offset of the vertex buffer. */
} * LPLSG_VERTEX_BUFFER, * const LPCLSG_VERTEX_BUFFER;
[spoiler]LSGDirectX9.h:#include "../Gfx/LSGDescStructs.h"
/** The 1D texture type. */
typedef IDirect3DTexture9 LSG_TEXTURE_1D;
/** The 2D texture type. */
typedef IDirect3DTexture9 LSG_TEXTURE_2D;
/** The 3D texture type. */
typedef IDirect3DTexture9 LSG_TEXTURE_3D;
/** The sampler type. */
typedef LSG_SAMPLER_DESC LSG_SAMPLER_STATE;
/** The shader resource view type. */
typedef IDirect3DTexture9 LSG_SHADER_RESOURCE;
/** The render target type. */
typedef IDirect3DSurface9 LSG_RENDER_TARGET;
/** The depth/stencil type. */
typedef IDirect3DSurface9 LSG_DEPTH_STENCIL;
/** The rasterizer state type. */
typedef LSG_RASTERIZER_DESC LSG_RASTERIZER_STATE;
/** The blend state type. */
typedef LSG_BLEND_DESC LSG_BLEND_STATE;
/** The depth/stencil state. */
typedef LSG_DEPTH_STENCIL_DESC LSG_DEPTH_STENCIL_STATE;
/** The viewport type. */
typedef D3DVIEWPORT9 LSG_VIEWPORT;
/** The scissor rectangle type. */
typedef RECT LSG_RECT;
/** The input layout type. */
typedef IDirect3DVertexDeclaration9 LSG_INPUT_LAYOUT;
/** The vertex buffer type. */
typedef struct LSG_VERTEX_BUFFER {
const CVertexBuffer * pvbVertBuffer; /**< The vertex buffer pointer. */
LSUINT32 ui32Offset; /**< Offset of the vertex buffer. */
} * LPLSG_VERTEX_BUFFER, * const LPCLSG_VERTEX_BUFFER;
LSGOpenGL.h:#include "../Gfx/LSGDescStructs.h"
/** The 1D texture type. */
typedef GLuint LSG_TEXTURE_1D;
/** The 2D texture type. */
typedef GLuint LSG_TEXTURE_2D;
/** The 3D texture type. */
typedef GLuint LSG_TEXTURE_3D;
/** The sampler type. */
typedef LSG_SAMPLER_DESC LSG_SAMPLER_STATE;
/** The shader resource view type. */
typedef GLuint LSG_SHADER_RESOURCE;
/** The render target type. */
typedef GLuint LSG_RENDER_TARGET;
/** The depth/stencil type. */
typedef GLuint LSG_DEPTH_STENCIL;
/** The rasterizer state type. */
typedef LSG_RASTERIZER_DESC LSG_RASTERIZER_STATE;
/** The blend state type. */
typedef LSG_BLEND_DESC LSG_BLEND_STATE;
/** The depth/stencil state. */
typedef LSG_DEPTH_STENCIL_DESC LSG_DEPTH_STENCIL_STATE;
/** The viewport type. */
typedef struct LSG_VIEWPORT {
GLint iX; /**< The left coordinate of the viewport in pixels. */
GLint iY; /**< The upper coordinate of the viewport in pixels. */
GLsizei sWidth; /**< Width of the viewport in pixels. */
GLsizei sHeight; /**< Height of the viewport in pixels. */
GLfloat fMinZ; /**< Together with MaxZ, value describing the range of depth values into which a scene is to be rendered, the minimum and maximum values of the clip volume. */
GLfloat fMaxZ; /**< Together with MinZ, value describing the range of depth values into which a scene is to be rendered, the minimum and maximum values of the clip volume. */
} * LPLSG_VIEWPORT, * const LPCLSG_VIEWPORT;
/** The scissor rectangle type. */
typedef struct LSG_RECT {
GLint left; /**< The x-coordinate of the upper-left corner of the rectangle. */
GLint top; /**< The y-coordinate of the upper-left corner of the rectangle. */
GLsizei right; /**< The x-coordinate of the lower-right corner of the rectangle. */
GLsizei bottom; /**< The y-coordinate of the lower-right corner of the rectangle. */
} * LPLSG_RECT, * const LPCLSG_RECT;
/** The input layout type. */
typedef LSVOID * LSG_INPUT_LAYOUT;
/** The vertex buffer type. */
typedef struct LSG_VERTEX_BUFFER {
const CVertexBuffer * pvbVertBuffer; /**< The vertex buffer pointer. */
LSUINT32 ui32Offset; /**< Offset of the vertex buffer. */
} * LPLSG_VERTEX_BUFFER, * const LPCLSG_VERTEX_BUFFER;
[/spoiler]The graphics device has 2 LSG_RENDER_STATE objects.
/** The last render state. */
LSG_RENDER_STATE CDirectX11::m_rsLastRenderState;
/** The current render state. */
LSG_RENDER_STATE CDirectX11::m_rsCurRenderState;
When you want to set a viewport (for example): LSE_INLINE LSVOID LSE_CALL CDirectX11::SetViewport( LSREAL _fX, LSREAL _fY,
LSREAL _fWidth, LSREAL _fHeight, LSUINT32 _ui32Target ) {
assert( _ui32Target < LSG_MAX_VIEWPORTS_SCISSORS );
assert( static_cast<FLOAT>(_fX) >= static_cast<FLOAT>(LSG_VIEWPORT_MIN) && static_cast<FLOAT>(_fX) <= static_cast<FLOAT>(LSG_VIEWPORT_MAX) );
assert( static_cast<FLOAT>(_fY) >= static_cast<FLOAT>(LSG_VIEWPORT_MIN) && static_cast<FLOAT>(_fY) <= static_cast<FLOAT>(LSG_VIEWPORT_MAX) );
assert( static_cast<FLOAT>(_fWidth) >= 0.0f && static_cast<FLOAT>(_fX + _fWidth) <= static_cast<FLOAT>(LSG_VIEWPORT_MAX) );
assert( static_cast<FLOAT>(_fHeight) >= 0.0f && static_cast<FLOAT>(_fY + _fHeight) <= static_cast<FLOAT>(LSG_VIEWPORT_MAX) );
m_rsCurRenderState.vViewports[_ui32Target].TopLeftX = static_cast<FLOAT>(_fX);
m_rsCurRenderState.vViewports[_ui32Target].TopLeftY = static_cast<FLOAT>(_fY);
m_rsCurRenderState.vViewports[_ui32Target].Width = static_cast<FLOAT>(_fWidth);
m_rsCurRenderState.vViewports[_ui32Target].Height = static_cast<FLOAT>(_fHeight);
}
m_rsCurRenderState is the current state and you can modify each part of it all you want. Redundant or not, it just updates the local copy.At some point you are going to want to make a draw call. This is when you check for redundancies.
LSVOID LSE_CALL CDirectX11::ApplyRenderStates( LSBOOL _bForce ) {
const LSG_RENDER_STATE & rsCurState = m_rsCurRenderState;
LSG_RENDER_STATE & rsLastState = m_rsLastRenderState;
#define LSG_CHANGED( MEMBER ) ((rsCurState.MEMBER != rsLastState.MEMBER) || _bForce)
#define LSG_UPDATE( MEMBER ) rsLastState.MEMBER = rsCurState.MEMBER
…
// Rasterizer state.
if ( LSG_CHANGED( prsRasterState ) ) {
GetDirectX11Context()->RSSetState( rsCurState.prsRasterState );
LSG_UPDATE( prsRasterState );
}
// Blend state.
if ( LSG_CHANGED( pbsBlendState ) ||
LSG_CHANGED( fBlendFactors[0] ) || LSG_CHANGED( fBlendFactors[1] ) || LSG_CHANGED( fBlendFactors[2] ) || LSG_CHANGED( fBlendFactors[3] ) ||
LSG_CHANGED( ui32SampleMask ) ) {
GetDirectX11Context()->OMSetBlendState( rsCurState.pbsBlendState,
rsCurState.fBlendFactors,
rsCurState.ui32SampleMask );
LSG_UPDATE( pbsBlendState );
LSG_UPDATE( fBlendFactors[0] );
LSG_UPDATE( fBlendFactors[1] );
LSG_UPDATE( fBlendFactors[2] );
LSG_UPDATE( fBlendFactors[3] );
LSG_UPDATE( ui32SampleMask );
}
// Depth/stencil state.
if ( LSG_CHANGED( pdssDepthStencilState ) || LSG_CHANGED( ui32StencilRef ) ) {
GetDirectX11Context()->OMSetDepthStencilState( rsCurState.pdssDepthStencilState,
rsCurState.ui32StencilRef );
LSG_UPDATE( pdssDepthStencilState );
LSG_UPDATE( ui32StencilRef );
}
…
// Viewports.
LSBOOL bSetViewports = LSG_CHANGED( ui8TotalViewportsAndScissors );
LSBOOL bSetScissors = bSetViewports;
if ( !bSetViewports ) {
// Check for changes in each viewport.
for ( LSUINT32 I = m_rsCurRenderState.ui8TotalViewportsAndScissors; I--; ) {
if ( LSG_CHANGED( vViewports[I].TopLeftX ) || LSG_CHANGED( vViewports[I].TopLeftY ) ||
LSG_CHANGED( vViewports[I].Width ) || LSG_CHANGED( vViewports[I].Height ) ||
LSG_CHANGED( vViewports[I].MinDepth ) || LSG_CHANGED( vViewports[I].MaxDepth ) ) {
bSetViewports = true;
break;
}
}
}
if ( bSetViewports ) {
GetDirectX11Context()->RSSetViewports( rsCurState.ui8TotalViewportsAndScissors,
rsCurState.vViewports );
// Update our copies of what we just sent to the hardware.
for ( LSUINT32 I = m_rsCurRenderState.ui8TotalViewportsAndScissors; I--; ) {
LSG_UPDATE( vViewports[I] );
}
}
…
#undef LSG_UPDATE
#undef LSG_CHANGED
}
LSVOID LSE_CALL CGfx::Render( void * _pibIndexBuffer,
LSUINT32 _ui32StartVertex,
LSUINT32 _ui32PrimitiveCount ) {
…
// Apply render states.
ApplyRenderStates();
// Render.
psShader->PreRender();
…
if ( _pibIndexBuffer ) {
VertexBuffers()[0].pvbVertBuffer->RenderApi( 0, 0 ); // Makes it apply certain necessary settings before rendering.
pibIndexBuffer->RenderApi( _ui32StartVertex, _ui32PrimitiveCount );
}
else {
VertexBuffers()[0].pvbVertBuffer->RenderApi( _ui32StartVertex, _ui32PrimitiveCount );
}
…
}
Redundancies are checked at the last minute, just before rendering. Hence it is last-minute.
L. Spiro
In order to create a model system, I need to choose a file format I am going to primarily support. I couldn't find too much information on which ones are used in the game industry, but it would seem .fbx is a good choice? Would it be possible to export models as compiled vertex and index buffers and then load them into the engines buffers? That would be very fast and efficient and it would make loading quite simple on the engine side (albeit not the exporter side, fbx is quite undocumented and I don't know where to start).
What should my hierarchy look like?
CModel
{
std::vector<CMesh*>mMeshes;
XMFLOAT3 mPos;
// what else?
};
CMesh
{
std::vector<CSubMesh*>mSubMeshes;
// what else?
};
CSubMesh
{
CMaterial* mMaterial;
vbuffer
ibuffer
pshader
vshader
// what else?
};
Thank you !
I couldn't find too much information on which ones are used in the game industry, but it would seem .fbx is a good choice?
Most engines use custom formats to store and load meshes at runtime. FBX is an interchange file format not optimized for runtime loading. Try to create a (binary) format specific to the needs of your engine that makes loading ultra fast, something like: <N><array of N vertices><...>, so when you load the model you can simply read the number of vertices and memcpy N*sizeof(Vertex) directly to the correct place (eg: graphics API vertex buffer).
My engine uses an hierarchy like this:
Actor
{
Model
Position
Actor specific constants
}
Model
{
Mesh
vector<Subset>
}
Mesh
{
Vertex Buffer
Index Buffer
}
Subset
{
Start index
Number of indices
Material
}
Material
{
Textures
Shader
Other data
}
This way you can have multiple Actors at different positions using the same model, and multiple models using the same mesh but with different materials, etc
Assimp does not limit 1 material per mesh - very roughly speaking what Assimp calls a Scene is what LSpiro is referring to as a mesh, and what Assimp calls a Mesh is what LSpiro is calling a Subset
Assimp loads the "Scene" as an array of "Meshes" which each have an index in to the array of Materials for the scene
This translates, in my engine, as a Mesh which contains an array of Submeshes, each with integer handles to the material.. Each Submesh can refer to only 1 material.. Each Material then contains an integer handle to the Shader responsible for drawing the material
When using components, don't call your resources components - anything that you have to load could be considered a resource - textures, meshes, animations, audio clips, shaders, etc
You only want to load these things once - and either pass around handles or pointers to these within the engine
IE if you want to have a Render component, it should contain a pointer or handle to a mesh rather than the mesh itself