模板函数分支优化



我试图写一个模板方法来创建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 ) );
            }
        }
    }

我认为应该有一种方法来做到这一点,因为着色器的类型是在编译时已知的,但我真的不知道如何(我的元编程技能还需要增长)。

<标题>注。

我在debugreleas设置下编译,并且在两个路径下都保留。

以下内容可能有所帮助:

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因为你不会每一帧都调用它。你只需要在设置的时候处理。

所以说shaderfragment_shadervertex_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无法看到更改的头文件。

在这种特殊情况下,除了构建设置没有其他答案…

最新更新