我试图写一个模板方法来创建Direct3D着色器。创建每种类型的着色器的API函数以及着色器的类型都有不同的名称。因此,我编写了以下代码:
class Shader final
{
public:
explicit Shader( _In_ ID3DBlob *const pBlob );
template <class T>
void Create
( std::weak_ptr<ID3D11Device>& pDevice
, CComPtr<T>& pResource )
{
auto p_Device = pDevice.lock();
if ( mp_Blob && p_Device )
{
HRESULT hr = E_FAIL;
ID3D11ClassLinkage* pClassLinkage = nullptr; // unsupported for now
pResource.Release();
CComPtr<ID3D11DeviceChild> pRes;
if ( std::is_same<T, ID3D11VertexShader>() )
{
hr = p_Device->CreateVertexShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11VertexShader**>( &pRes ) );
}
else if ( std::is_same<T, ID3D11HullShader>() )
{
hr = p_Device->CreateHullShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11HullShader**>( &pRes ) );
}
else if ( std::is_same<T, ID3D11DomainShader>() )
{
hr = p_Device->CreateDomainShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11DomainShader**>( &pRes ) );
}
else if ( std::is_same<T, ID3D11GeometryShader>() )
{
hr = p_Device->CreateGeometryShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11GeometryShader**>( &pRes ) );
}
else if ( std::is_same<T, ID3D11ComputeShader>() )
{
hr = p_Device->CreateComputeShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11ComputeShader**>( &pRes ) );
}
else if ( std::is_same<T, ID3D11PixelShader>() )
{
hr = p_Device->CreatePixelShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11PixelShader**>( &pRes ) );
}
else
{
assert( false
&& "Need a pointer to an ID3D11 shader interface" );
}
//TODO: log hr's error code.
assert( SUCCEEDED( hr ) && "Error: shader creation failed!" );
if ( FAILED( hr ) )
{
pResource.Release();
}
else
{
hr = pRes->QueryInterface( IID_PPV_ARGS( &pResource ) );
assert( SUCCEEDED( hr ) );
}
}
}
private:
CComPtr<ID3DBlob> mp_Blob;
};
它应该工作,虽然我还没有测试它。但问题是编译器不会丢弃那些肯定不会被采用的分支路径。例如:
CComPtr<ID3D11DomainShader> pDS;
//pShader is an instance of Shader class
pShader->Create(pDevice, pDs);
将创建一个域着色器。但是编译器会在生成的函数中保留所有的路径,而不是只生成
void Create
( std::weak_ptr<ID3D11Device>& pDevice
, CComPtr<ID3D11DomainShader>& pResource )
{
auto p_Device = pDevice.lock();
if ( mp_Blob && p_Device )
{
HRESULT hr = E_FAIL;
ID3D11ClassLinkage* pClassLinkage = nullptr; // unsupported for now
pResource.Release();
CComPtr<ID3D11DeviceChild> pRes;
if ( true ) // this is the evaluation of std::is_same<ID3D11DomainShader, ID3D11DomainShader>()
{
hr = p_Device->CreateDomainShader
( mp_Blob->GetBufferPointer()
, mp_Blob->GetBufferSize()
, pClassLinkage
, reinterpret_cast<ID3D11DomainShader**>( &pRes ) );
}
//TODO: log hr's error code.
assert( SUCCEEDED( hr ) && "Error: shader creation failed!" );
if ( FAILED( hr ) )
{
pResource.Release();
}
else
{
hr = pRes->QueryInterface( IID_PPV_ARGS( &pResource ) );
assert( SUCCEEDED( hr ) );
}
}
}
我认为应该有一种方法来做到这一点,因为着色器的类型是在编译时已知的,但我真的不知道如何(我的元编程技能还需要增长)。
<标题>注。我在debug
和releas
设置下编译,并且在两个路径下都保留。
以下内容可能有所帮助:
HRESULT createShader(
ID3D11Device& pDevice,
CComPtr<ID3D11VertexShader>& pResource,
CComPtr<ID3D11DeviceChild> pRes)
{
return p_Device.CreateVertexShader(
mp_Blob->GetBufferPointer(),
mp_Blob->GetBufferSize(),
pClassLinkage,
reinterpret_cast<ID3D11VertexShader**>(&pRes));
}
// similar for other Shader type
template <class T>
void Create(
std::weak_ptr<ID3D11Device>& pDevice,
CComPtr<T>& pResource)
{
auto p_Device = pDevice.lock();
if (!mp_Blob || !p_Device) {
return;
}
pResource.Release();
CComPtr<ID3D11DeviceChild> pRes;
// ---------------- 8< --------------------
// Here is the change: no more `if` to check type,
// let the compiler choose the correct overload
HRESULT hr = createShader(*p_device, pResource, pRes);
// ---------------- >8 --------------------
assert( SUCCEEDED( hr ) && "Error: shader creation failed!" );
if ( FAILED( hr ) ) {
pResource.Release();
} else {
hr = pRes->QueryInterface( IID_PPV_ARGS( &pResource ) );
assert( SUCCEEDED( hr ) );
}
}
关于优化:我认为您对创建代码来处理任何模板类型都感到不安。在我的元编程解决方案中,您需要将is_same
逻辑转移到enable_if
,然后与您想要的模板匹配的函数将仅是您想要的代码。
然而,我认为你的问题仍然是一个过于抽象的问题,如果底层动物是猴子,你不能使用Animal
类只接受Banana
。
(在这个经典的例子中,Monkey
来源于Animal
, Banana
来源于Food
,其中Animal
有一个方法void eat(Food)
)
如何做好你想做的事的答案
有点长,所以我略读了一下。
请记住元编程并不是总能解决问题(在很多情况下,您知道类型,但程序不知道,例如数据库结果集中的列)。
高性能
首先不要让未知类型进入。下面是一个常见的模式:
class unverified_thing: public base_class {
public:
unverified_thing(base_class* data): data(data) { type_code = -1; }
void set_type_code(int to) { /*throw if not -1*/ type_code = to; }
derived_A* get_as_derived_A() const { /*throw if not the right type code*/
return *(derived_A*)data;
}
derived_B* get_as_derived_B() const { /*throw is not right type code*/
return *(derived_B*)data;
}
//now do the base class methods
whatever base_class_method() {
return data->base_class_method();
}
private:
int type_code;
base_class data;
};
现在您可以假设unverified_thing
是您的数据,并且您已经引入了一种类型检查形式。你可以引入getter因为你不会每一帧都调用它。你只需要在设置的时候处理。
所以说shader
是fragment_shader
和vertex_shader
的基类,你可以处理shader
,但已经设置了type_id,所以你可以处理shader
的权利,直到你编译你的着色器,然后你可以转换到正确的派生类型,如果错误的运行时错误。这避免了c++ RTTI,它可能相当沉重。
记住你可以负担得起设置时间,你要确保你发送到引擎的每一位数据都是正确的。
此类型模式来自仅允许通过的经过验证的输入(这阻止了许多错误),您有一个不来自数据类型的unverified_thing
,如果您将类型设置为已验证,则只能提取无错误的数据。
一个更好的方法(但可能会很快变得混乱)是:
template<bool VERIFIED=true>
class user_input { };
/*somewhere in your dialog class (or whatever)*/
user_input<false> get_user_input() const { /*whatever*/ }
/*then have somewhere*/
user_input verify_input(const user_input<false>& some_input) { /*which will throw as needed*/ }
对于user_input的大数据类,将large_data*
隐藏在user_input
类中是很好的,但是您可以理解。
使用元编程(限制最终结果对用户输入的灵活性)
template<class U>
typename ::std::enable_if<my_funky_criteria<U>::value,funky_shader>::type
Create(::std::istream& input) { /*blah*/ }
template<class U>
struct my_funky_criteria: typename ::std::conditional</*what you want*/,::std::true_type,::std::false_type>::type { };
这个必须是编译器设置问题,即使您声明您使用发布模式。您是否检查过在发布模式配置中使用的是/O3而不是/O2 ?O2对大小进行了优化,也许可以重用相同的二进制文件,而不是为每种类型创建一个版本(尽管我不确定它是否被标准禁止)。
另外,检查反汇编器窗口,看看IDE是否欺骗了您。重建你的项目,等等。有时Visual Studio无法看到更改的头文件。
在这种特殊情况下,除了构建设置没有其他答案…