模板功能分支优化

时间:2014-07-28 20:42:07

标签: c++ templates c++11 directx

我正在尝试编写一个模板方法来为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 ) );
            }
        }
    }

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

P.S。

我在debugreleas设置中都进行了编译,并保留了两个路径。

3 个答案:

答案 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_thingshaderfragment_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无法看到更改的头文件。

在这种特殊情况下,除了构建设置之外别无其他答案......