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 是:-
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);
定义声明 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
并定义快速错误检查:-
在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__)
场景:
因此,过去 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 是:-
D3D11 基本代码框架:-
在D3DApp.hclass 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);
定义声明 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
并定义快速错误检查:-
在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__)