Why are you using virtual functions and abstract base classes? You aren’t going to compile both Metal and Direct3D 11 into the same executable and there will be no run-time instantiation of these types. You can-but-won’t compile Vulkan into the same executable since it would be PC-only—you would still have to build specific executables for Mac OS X/iOS and Linux without Direct 3D 11, so you would already have each API able to compile-out and it would be senseless to add extra complexity via special cases.
If you are only building for one API then you have even less of a reason.
The purpose of polymorphism is entirely run-time. You will never change the renderer type at run-time, and never not know which classes are actually being used, so your design is purely a violation of polymorphism. Stop it.
None of the functions in your example need to be virtual.
In every executable you build there will be one possible set of classes each using a common interface. So build your class hierarchy and structure under the assumption that there will only be one API for rendering.
Here are examples I have posted before:
/**
* Class CIndexBufferBase
* \brief The base class for index buffers.
*
* Description: The base class for index buffers.
*/
class CIndexBufferBase {
public :
// == Functions.
/**
* Resets the base members back to their defaults.
*/
void Reset();
/**
* Gets the ID of this vertex buffer segment.
*
* \return Returns the ID of this vertex buffer segment.
*/
__inline uint32_t GetId() const;
/**
* Gets the topology.
*
* \return Returns the topology.
*/
__inline uint32_t Topology() const;
/**
* Gets the element size.
*
* \return Returns the size in bytes of the indices.
*/
__inline uint32_t IndexSize() const;
/**
* Gets the total number of indices in the index buffer.
*
* \return Returns the total number of indices in the index buffer.
*/
__inline uint32_t TotalIndices() const;
/**
* Gets the number of primitives.
*
* \return Returns the number of primitives.
*/
__inline uint32_t Primitives() const;
protected :
// == Various constructors.
CIndexBufferBase();
~CIndexBufferBase();
// == Members.
/** Size of each element in the buffer. */
uint32_t m_ui32ElementSize;
/** Usage type. */
uint32_t m_ui32Usage;
/** Number of indices. */
uint32_t m_ui32TotalIndices;
/** The total number of primitives in the vertex buffer. */
uint32_t m_ui32PrimitiveCount;
/** Topology. */
uint32_t m_ui32Topology;
/** The index buffer ID. */
uint32_t m_ui32Id;
};
/**
* Class CDirectX11IndexBuffer
* \brief A Direct3D 11 index buffer.
*
* Description: A Direct3D 11 index buffer.
*/
class CDirectX11IndexBuffer : public CIndexBufferBase {
public :
// == Functions.
protected :
// == Various constructors.
CDirectX11IndexBuffer();
~CDirectX11IndexBuffer();
// == Members.
/** Our index buffer used with Direct3D 11. */
ID3D11Buffer * m_pbDirectX11IndexBuffer;
/** The format of the index buffer. */
DXGI_FORMAT m_fFormat;
// == Functions.
/**
* Create an index buffer for the graphics API. This should use the data in this object to create
* the buffer.
*
* \return Returns false if there are any errors during the creation of the API index buffer. This
* always indicates a lack of resources.
*/
bool CreateApiIndexBuffer();
/**
* Destroy the index buffer that is compliant with the current graphics API and all related
* resources.
*/
void ResetApi();
/**
* Render. This is performed on the first vertex buffer set. All other vertex buffers must use the same primitive type
* and have the same number of elements active. If not, the system will throw an error.
*
* \param _ui32StartVertex Index of the first vertex to load. Beginning at _ui32StartVertex the correct number of vertices will be read out of the vertex buffer.
* \param _ui32TotalPrimitives The total number of primitives to render.
*/
void RenderApi( LSUINT32 _ui32StartVertex, LSUINT32 _ui32TotalPrimitives ) const;
};
Same protected API for CDirectX9IndexBuffer, CMetalIndexBuffer, CVulkanIndexBuffer, COpenGlIndexBuffer, and COpenGlEsIndexBuffer.
Each includes API-specific types for that rendering API. ID3D11Buffer and DXGI_FORMAT shown here.
/**
* Class CIndexBuffer
* \brief An index buffer.
*
* Description: An index buffer.
*/
class CIndexBuffer : public
#ifdef LSG_DX9
CDirectX9IndexBuffer
#elif defined( LSG_DX11 )
CDirectX11IndexBuffer
#elif defined( LSG_OGL )
COpenGlIndexBuffer
#elif defined( LSG_OGLES )
COpenGlEsIndexBuffer
#elif defined( LSG_METAL )
CMetalIndexBuffer
#elif defined( LSG_VKN )
CVulkanIndexBuffer
#endif // #ifdef LSG_DX9
{
public :
// == Various constructors.
CIndexBuffer();
~CIndexBuffer();
// == Functions.
/**
* Creates an index buffer.
*
* \param _pvIndices Pointer to the actual index data.
* \param _ui32TotalIndices Total number of indices to which _pvIndices points.
* \param _ui32IndexSize Size, in bytes, of each index in the buffer.
* \param _bTakeOwnership If true, the index buffer keeps a copy of _pvIndices for itself. If false,
* the contents of _pvIndices are used directly by the index buffer.
* \param _ibutUsage One of the LSG_INDEX_BUFFER_USAGE_TYPES flags.
* \param _ptTopology The primitive topology.
* \return Returns true if the index buffer was created. False indicates a memory/resource failure.
*/
bool CreateIndexBuffer( const void * _pvIndices,
uint32_t _ui32TotalIndices, uint32_t _ui32IndexSize,
bool _bTakeOwnership,
LSG_INDEX_BUFFER_USAGE_TYPES _ibutUsage,
LSG_PRIMITIVE_TOPOLOGY _ptTopology );
/**
* Resets the vertex buffer back to scratch.
*/
void Reset();
protected :
// == Members.
private :
#ifdef LSG_DX9
typedef CDirectX9IndexBuffer Parent;
#elif defined( LSG_DX11 )
typedef CDirectX11IndexBuffer Parent;
#elif defined( LSG_METAL )
typedef CMetalIndexBuffer Parent;
#elif defined( LSG_OGL )
typedef COpenGlIndexBuffer Parent;
#elif defined( LSG_OGLES )
typedef COpenGlEsIndexBuffer Parent;
#elif defined( LSG_VKN )
typedef CVulkanIndexBuffer Parent;
#endif // #ifdef LSG_DX9
};
This is a simple hierarchy. The “magic” happens because all API-specific classes (CDirectX11IndexBuffer, CDirectX9IndexBuffer, CMetalIndexBuffer, CVulkanIndexBuffer, COpenGlIndexBuffer, and COpenGlEsIndexBuffer) are guaranteed to provide the same set of protected functions.
So, here is the implementation for CIndexBuffer::CreateIndexBuffer().
bool CIndexBuffer::CreateIndexBuffer( const void * _pvIndices,
uint32_t _ui32TotalIndices, uint32_t _ui32IndexSize,
bool _bTakeOwnership,
LSG_INDEX_BUFFER_USAGE_TYPES _ibutUsage,
LSG_PRIMITIVE_TOPOLOGY _ptTopology ) {
Reset();
m_ui32TotalIndices = _ui32TotalIndices;
m_ui32ElementSize = _ui32IndexSize;
m_ui32Usage = _ibutUsage;
m_ui32PrimitiveCount = CVertexBuffer::PrimitiveCount( _ptTopology, _ui32TotalIndices );
m_ui32Topology = _ptTopology;
uint32_t ui32Size = m_ui32ElementSize * m_ui32TotalIndices;
if ( !ui32Size ) { Reset(); return false; }
// Other verification etc.
if ( !CreateApiIndexBuffer() ) {
Reset();
return false;
}
return true;
}
CIndexBuffer can call CreateApiIndexBuffer() because it is guaranteed to be implemented in the API classes. Parameters are verified and copied in CIndexBuffer because it is the same across all API’s.
No run-time overhead caused by virtual functions. No duplication of data or logic, etc.
As was mentioned earlier, eliminate your macros, and don’t define custom values unless you need to. Again, translating your values into API-specific values is needless run-time overhead.
For Direct3D 11:
/** Usage types. */
enum LSG_INDEX_BUFFER_USAGE_TYPES {
LSG_IBUT_SETONLY = D3D11_USAGE_IMMUTABLE, /**< Index data is never read or written to. */
LSG_IBUT_STANDARD = D3D11_USAGE_DEFAULT, /**< Index data is not read or written to often by the CPU. */
LSG_IBUT_DYNAMIC = D3D11_USAGE_DYNAMIC, /**< Index data is updated frequently. */
LSG_IBUT_FORCE_DWORD = 0x7FFFFFFF
};
For OpenGL:
/** Usage types. */
enum LSG_INDEX_BUFFER_USAGE_TYPES {
LSG_IBUT_SETONLY = GL_STATIC_DRAW, /**< Index data is never read or written to. */
LSG_IBUT_STANDARD = GL_STREAM_DRAW, /**< Index data is not read or written to often by the CPU. */
LSG_IBUT_DYNAMIC = GL_DYNAMIC_DRAW, /**< Index data is updated frequently. */
LSG_IBUT_FORCE_DWORD = 0x7FFFFFFF
};
For Vulkan:
/** Usage types. */
enum LSG_INDEX_BUFFER_USAGE_TYPES {
LSG_IBUT_SETONLY = (1 << 0), /**< Index data is never read or written to. */
LSG_IBUT_STANDARD = (1 << 1), /**< Index data is not read or written to often by the CPU. */
LSG_IBUT_DYNAMIC = (1 << 2), /**< Index data is updated frequently. */
LSG_IBUT_FORCE_DWORD = 0x7FFFFFFF
};
Note that forcing the size of an enumeration via 0x7FFFFFFF is deprecated; you should be using C++11 features to do this but I have not updated my code.
Another example.
Direct3D 11:
/** The blend types we support. These are for blending to the backbuffer only. */
enum LSG_BLEND_MODE {
LSG_BM_ZERO = D3D11_BLEND_ZERO, /**< Blend factor is (0, 0, 0, 0). */
LSG_BM_ONE = D3D11_BLEND_ONE, /**< Blend factor is (1, 1, 1, 1). */
LSG_BM_SRC_COLOR = D3D11_BLEND_SRC_COLOR, /**< Blend factor is (Rs, Gs, Bs, As). */
LSG_BM_INV_SRC_COLOR = D3D11_BLEND_INV_SRC_COLOR, /**< Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As). */
LSG_BM_DST_COLOR = D3D11_BLEND_DEST_COLOR, /**< Blend factor is (Rd, Gd, Bd, Ad). */
LSG_BM_INV_DST_COLOR = D3D11_BLEND_INV_DEST_COLOR, /**< Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad). */
LSG_BM_SRC_ALPHA = D3D11_BLEND_SRC_ALPHA, /**< Blend factor is (As, As, As, As). */
LSG_BM_INV_SRC_ALPHA = D3D11_BLEND_INV_SRC_ALPHA, /**< Blend factor is (1 - As, 1 - As, 1 - As, 1 - As). */
LSG_BM_DST_ALPHA = D3D11_BLEND_DEST_ALPHA, /**< Blend factor is (Ad Ad Ad Ad). */
LSG_BM_INV_DEST_ALPHA = D3D11_BLEND_INV_DEST_ALPHA, /**< Blend factor is (1 - Ad 1 - Ad 1 - Ad 1 - Ad). */
LSG_BM_SRC_ALPHA_SAT = D3D11_BLEND_SRC_ALPHA_SAT, /**< Blend factor is (f, f, f, 1), where f = min(As, 1 - Ad). */
LSG_BM_FORCE_DWORD = 0x7FFFFFFF
};
Vulkan:
/** The blend types we support. These are for blending to the backbuffer only. */
enum LSG_BLEND_MODE {
LSG_BM_ZERO = VK_BLEND_FACTOR_ZERO, /**< Blend factor is (0, 0, 0, 0). */
LSG_BM_ONE = VK_BLEND_FACTOR_ONE, /**< Blend factor is (1, 1, 1, 1). */
LSG_BM_SRC_COLOR = VK_BLEND_FACTOR_SRC_COLOR, /**< Blend factor is (Rs, Gs, Bs, As). */
LSG_BM_INV_SRC_COLOR = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, /**< Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As). */
LSG_BM_DST_COLOR = VK_BLEND_FACTOR_DST_COLOR, /**< Blend factor is (Rd, Gd, Bd, Ad). */
LSG_BM_INV_DST_COLOR = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, /**< Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad). */
LSG_BM_SRC_ALPHA = VK_BLEND_FACTOR_SRC_ALPHA, /**< Blend factor is (As, As, As, As). */
LSG_BM_INV_SRC_ALPHA = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, /**< Blend factor is (1 - As, 1 - As, 1 - As, 1 - As). */
LSG_BM_DST_ALPHA = VK_BLEND_FACTOR_DST_ALPHA, /**< Blend factor is (Ad Ad Ad Ad). */
LSG_BM_INV_DEST_ALPHA = VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, /**< Blend factor is (1 - Ad 1 - Ad 1 - Ad 1 - Ad). */
LSG_BM_SRC_ALPHA_SAT = VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, /**< Blend factor is (f, f, f, 1), where f = min(As, 1 - Ad). */
LSG_BM_FORCE_DWORD = 0x7FFFFFFF
};
Now you can plug LSG_BLEND_MODE values directly into an API function (may need a static_cast<>()) without run-time conversions unless necessary (Vulkan and Metal LSG_INDEX_BUFFER_USAGE_TYPES, for example).
L. Spiro