C++ [D3D11] - 奇怪的行为,函数被调用了 2 次

C++ [D3D11] - Weird behavior, function is called 2 times

场景:
因此,过去 15 天我一直在学习 D3D11 编程,现在使用 Frank Luna 的“使用 DirectX11 进行 3d 游戏编程”一书。到目前为止,我做得很好。但是因为本书使用了一个已弃用的 D3DX11 库,所以我不得不设计自己的函数作为替代。

其中之一是编译着色器的功能。是这样的——
Helpers.cpp

#include "Helpers.h"


HRESULT CompileShader(LPCWSTR srcFile, LPCSTR entryPoint, LPCSTR profile, ID3DBlob** blob)
{
    if(!srcFile || !entryPoint || !profile || !blob)
        return E_INVALIDARG;

    *blob = nullptr;

    UINT flags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined(DEBUG) || defined(_DEBUG)
    flags |= D3DCOMPILE_DEBUG;
#endif

    ComPtr<ID3DBlob> errorBlob;

    HRESULT hr = D3DCompileFromFile(
        srcFile, nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, 
        entryPoint, profile, flags, 0, &blob, errorBlob.GetAddressOf());

    if(errorBlob.Get())
    {
        MessageBoxA(0, (char*)errorBlob->GetBufferPointer(), "Shader ERROR/WARNING", MB_ICONINFORMATION);
        errorBlob.Reset();
    }

    if(FAILED(hr))
    {
        (*blob)->Release();

        std::wostringstream ss;
        ss << L"Error in compiling shader: " << srcFile;
        MessageBox(0, ss.str().c_str(), L"ERROR", MB_ICONERROR);

        return hr;
    }

    return hr;
}

注意:文件 Helpers.h 只包含此函数声明和一些包含。



其他一些助手 functions/defines 是:-

  1. D3D11 基本代码框架:-
    D3DApp.h

    class D3DApp
    {
    protected:
         //Win32 related
         HWND hWnd;
         HINSTANCE hInst;
         LPWSTR appName;
         LPWSTR const className;
    
         int wndWidth;
         int wndHeight;
    
         //Direct3D 11 related
         ComPtr<ID3D11Device> pDevice;
         ComPtr<ID3D11DeviceContext> pContext;
         ComPtr<IDXGISwapChain> pSwapChain;
         ComPtr<ID3D11RenderTargetView> pRenderTargetView;
         ComPtr<ID3D11DepthStencilView> pDepthStencilView;
         ComPtr<ID3D11Texture2D> pDepthStencilBuffer;
         D3D11_VIEWPORT viewport;
    
         //Can be used to tweak d3d11 settings
         bool enable4xMSAA;
         bool enableFullscreen;
    
         //App states
         bool isAppPaused;
         bool isResizing;
    
         //Others
         Timer timer;
         FrameStats stats;
    
         bool isDestroyed;
    
     public:
         D3DApp();
         ~D3DApp();
    
         virtual bool init();
         virtual bool onResize();
         virtual void update(float dt) = 0;
         virtual void render() = 0;
         virtual void destroy();
         virtual void run();
    
         virtual LRESULT msgProc(HWND, UINT, WPARAM, LPARAM);
    
         virtual void onMouseDown(WPARAM btnState, int x, int y) {}
         virtual void onMouseUp(WPARAM btnState, int x, int y) {}
         virtual void onMouseMove(WPARAM btnState, int x, int y) {}
    
         virtual void onKeyDown(WPARAM keyState) {};
         virtual void onKeyUp(WPARAM keyState) {};
    
     private:
         //Implementation meant to be hidden
         //Not needed to be changed often
         bool _initWindow();
         bool _initD3D();
     };
    
     extern D3DApp* gD3DApp;
     LRESULT CALLBACK gMsgProc(HWND, UINT, WPARAM, LPARAM);
    
  2. 定义声明 main() :-
    D3DApp.h

    #ifndef IMPLEMENT_D3DAPP_MAIN
    #define IMPLEMENT_D3DAPP_MAIN(childClass) \
    INT CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, INT nShowCmd) \
    { \
        childClass app; \
        try { \
            if(!app.init()) \
            { \
                app.destroy(); \
                return 1; \
            } \
            app.run(); \
        } \
        catch(std::exception& e) \
        { \
            OutputDebugStringA(e.what()); \
            MessageBoxA(0, e.what(), "ERROR", MB_ICONERROR); \
            app.destroy(); \
            return 1; \
        } \
        app.destroy(); \
        return 0; \
    }
    #endif //IMPLEMENT_D3DAPP_MAIN
    
  3. 并定义快速错误检查:-
    D3DApp.h

    #define HR(hr) \ if(FAILED(hr)) { \ std::ostringstream ss; \ ss << "FILE: " << __FILE__ << std::endl << std::endl; \ ss << "LINE: " << __LINE__ << std::endl << std::endl; \ ss << "HRESULT = " << (hr) << std::endl; \ throw std::exception(ss.str().c_str()); \ }

它的工作方式类似于函数调用失败。它由 HR() 检测到并抛出 std::exception,然后在 WinMain() try-catch 块中捕获。然后用户可以在 MessageBox 中看到错误。

它应该很好用。

但在我的应用程序中:-
main.cpp

#include "../Common/D3DApp.h"
#include "../Common/Helpers.h"
#include <sstream>
#include <DirectXMath.h>
#include <DirectXColors.h>


using namespace DirectX;


class Drawing : public D3DApp
{
public:
    ComPtr<ID3D11PixelShader> PS;
    ComPtr<ID3D11VertexShader> VS;

public:
    bool init() override
    {
        appName = L"Drawing in D3D11";
        if(!D3DApp::init())
            return false;

        //////////////////////////////////////////
        //////////////SHADERS////////////////////
        ComPtr<ID3DBlob> shaderBytecode;

        HR(CompileShader(L"simple.lsl", "VSMain", "vs_5_0", shaderBytecode.GetAddressOf()));
        HR(pDevice->CreateVertexShader(shaderBytecode->GetBufferPointer(), shaderBytecode->GetBufferSize(), nullptr, VS.GetAddressOf()));
        shaderBytecode.Reset();

        HR(CompileShader(L"simple.hlsl", "PSMain", "ps_5_0", shaderBytecode.GetAddressOf()));
        HR(pDevice->CreatePixelShader(shaderBytecode->GetBufferPointer(), shaderBytecode->GetBufferSize(), nullptr, PS.GetAddressOf()));
        shaderBytecode.Reset();
        //////////////SHADERS////////////////////

        //////////////SET STATES////////////////
        pContext->PSSetShader(PS.Get(), nullptr, 0);
        pContext->VSSetShader(VS.Get(), nullptr, 0);

        pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        //////////////SET STATES////////////////

        /////////////////////////////////////////

        return true;
    }

    void update(float deltaTime) override
    {
    }

    void render() override
    {
        const float clearColour[4] = {0.f, 0.f, 1.f, 1.f};
        pContext->ClearRenderTargetView(pRenderTargetView.Get(), clearColour);
        pContext->ClearDepthStencilView(pDepthStencilView.Get(), D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.f, 1);

        pContext->Draw(3, 0);

        HR_PRESENT(pSwapChain->Present(0, 0));
    }

    void destroy() override
    {
        VS.Reset();
        PS.Reset();

        D3DApp::destroy();
    }
};

IMPLEMENT_D3DAPP_MAIN(Drawing);

为了测试 HR() 定义,我 故意 使从 "simple.hlsl" 到 [=39= 的函数参数无效]"simple.lsl",在编译vertex shader的时候可以看到

结果:
1. 创建 window。 [确定]
2. 弹出窗口显示 "Error in compiling shader: simple.lsl" [OK]
3. [点击确定]
4. 同样的弹出窗口再次显示相同的消息 [WTH?]
5. [点击确定]
6. 最后弹出try-catch块,描述错误的文件、行和HRESULT [OK]

抱歉,如果我的英语很乏味。
这似乎是一个小问题。是的,我可以忍受这个。但这非常烦人。感谢您阅读到这里 ;)
提前致谢。

让我们看看 HR 宏:

#define HR(hr) \
if(FAILED(hr)) { \
    std::ostringstream ss; \
    ss << "FILE: " << __FILE__ << std::endl << std::endl; \
    ss << "LINE: " << __LINE__ << std::endl << std::endl; \
    ss << "HRESULT = " << (hr) << std::endl; \
    throw std::exception(ss.str().c_str()); \
}

您在函数中使用 "argument" hr 两次 ,一次在 FAILED(hr) 中,一次在打印结果时。这意味着 hr 将被评估两次。如果您使用函数调用作为宏参数,那么该函数将被调用两次。

这是因为宏在 运行 时不是 "called",而是 替换 C++ 解析器到达代码。

这是 一个 宏经常被反对的主要原因,尤其是在 C++ 中。

展开HR(CompileShader(L"simple.lsl", "VSMain", "vs_5_0", shaderBytecode.GetAddressOf()));:

if(FAILED(CompileShader(L"simple.lsl", "VSMain", "vs_5_0", shaderBytecode.GetAddressOf())))) { \
    std::ostringstream ss; \
    ss << "FILE: " << __FILE__ << std::endl << std::endl; \
    ss << "LINE: " << __LINE__ << std::endl << std::endl; \
    ss << "HRESULT = " << (CompileShader(L"simple.lsl", "VSMain", "vs_5_0", shaderBytecode.GetAddressOf()))) << std::endl; \
    throw std::exception(ss.str().c_str()); \
};

你现在可以看到问题了。

既然你想要__FILE____LINE__,你可以使用一个以文件和行为参数的函数

void HR_check(HRESULT hr, const char* file, int line)
{
    if (FAILED(hr)) {
        std::ostringstream ss;
        ss << "FILE: " << file << std::endl << std::endl;
        ss << "LINE: " << line << std::endl << std::endl;
        ss << "HRESULT = " << hr << std::endl;
        throw std::exception(ss.str().c_str());
    }
}

并将其与宏组合:

#define HR(fn) HR_check(fn, __FILE__, __LINE__)