我正在尝试编写一个模板方法来为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
设置中都进行了编译,并保留了两个路径。
答案 0 :(得分:5)
以下可能会有所帮助:
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 ) );
}
}
答案 1 :(得分:1)
关于您的优化:
我认为你对创建的代码感到不安,无论哪种模板类型都可以处理。
您需要将我的is_same
逻辑转移到我的元编程解决方案中的enable_if
,然后与您想要的模板匹配的函数将只是您想要的代码。
但是,我仍然将您的问题解释为抽象过多的问题,如果基础动物是猴子,则不能使用Animal
类仅接受Banana
。
(在这个经典示例中,Monkey
来自Animal
和来自Banana
的{{1}},其中Food
有方法Animal
)< / p>
回答如何做你想做的事
有点长,所以我撇去它。
请记住,元编程并不总能节省时间(在很多情况下,您知道类型但程序不知道,例如数据库结果集中的列)。
高效
首先不要让未知类型。这是一个常见的模式:
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
是shader
和fragment_shader
的基类,你可以处理vertex_shader
但是已经设置了type_id,所以你可以处理{ {1}}直到您编译着色器,然后如果错误,您可以使用运行时错误转换为正确的派生类型。这避免了C ++ RTTI,它可能非常繁重。
请记住,您可以负担设置时间,您希望确保发送到引擎的每一位数据都正确无误。
这种类型模式来自经过验证的输入,只允许通过(停止了很多错误)你有shader
不是从数据类型派生的,你只能在没有错误的情况下提取数据要验证的类型。
更好的方法是做到这一点(但可能会变得很乱):
shader
对于user_input的大型数据类,最好在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*/ }
,但是你明白了。
使用元编程(限制最终结果的灵活性。用户输入)
large_data*
与
user_input
答案 2 :(得分:0)
此必须是编译器设置问题,即使您声明使用了发布模式。您是否检查过您在发布模式配置中使用/ O3而不是/ O2? O2优化了大小,也许可以重复使用相同的二进制文件,而不是为每种类型创建一个版本(即使我不确定它是否被标准禁止)。
另外,检查反汇编程序窗口以查看IDE是否只是欺骗了您。并重建您的项目等。有时Visual Studio无法看到更改的头文件。
在这种特殊情况下,除了构建设置之外别无其他答案......