本文主要是介绍DirectX12(D3D12)基础教程(二十三) ——DirectShaderCompiler 头文件接口 ID3DInclude 的应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 1、前言
- 2、ID3DInclude 回调接口介绍
- 3、基本使用方法
- 4、示例代码中的实现
1、前言
本章教程较短小,但内容十分重要,是后续更灵活使用 Shader 编程的重要基础之一。也就是对 Shader 代码进行头文件分离复用设计进行全面支持。或者直白的说,本章内容重点就是让各位掌握以编程的方式在代码中支持 Shader 头文件的方法,方便在设计 Shader 编辑器之类工具时,可以让所有的 Shader 组织的更有层次。本章实例代码依然基于示例 GRSD3D12Sample/25-IBL-MultiInstance-Sphere
2、ID3DInclude 回调接口介绍
这个接口是个很有意思的接口,名字看上去是很 COM 化的,但其实跟 COM 没啥关系,只是一个定义了两个纯虚接口函数的类而已,其详细定义摘录如下:
typedef interface ID3DInclude ID3DInclude;
#undef INTERFACE
#define INTERFACE ID3DInclude
DECLARE_INTERFACE(ID3DInclude)
{STDMETHOD(Open)(THIS_ D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes) PURE;STDMETHOD(Close)(THIS_ LPCVOID pData) PURE;
};
typedef ID3DInclude* LPD3DINCLUDE;
ID3DInclude
是从Direct3D 10及更高版本中引入的回调接口,它用于处理着色器编译过程中的文件包含操作,即 “#include” 宏命令。在着色器代码中,我们经常需要包含其他文件,例如头文件,来共享常用的函数或变量定义。ID3DInclude
接口允许应用程序为着色器编译器提供自定义的文件包含逻辑。
ID3DInclude
接口定义在 d3dcompiler.h
头文件中,并且通常与 D3DCompile
或 D3DCompileFromFile
函数一起使用,这些函数是用于编译着色器的主要函数。
下面是 ID3DInclude
接口中定义的主要方法:
-
Open:当编译器遇到
#include
指令时,它会调用此方法。应用程序应该实现此方法以打开指定的文件,并返回一个指向文件内容的指针。 -
Close:在编译器完成处理一个包含的文件后,它会调用此方法。应用程序应该实现此方法以关闭文件并释放任何关联的资源。
需要注意的是,仅包含这两个函数的版本是用于D3D12配套版本的着色器编译器的。
3、基本使用方法
为了使用自定义的包含逻辑,你需要实现一个类,该类继承自 ID3DInclude
接口,并实现上述所有方法。然后,在调用 D3DCompile
或 D3DCompileFromFile
时,你可以将你的实现作为参数传递。
下面是一个简单的示例,展示了如何实现 ID3DInclude
接口:
#include <d3dcompiler.h>
class MyInclude : public ID3DInclude
{
public:virtual HRESULT STDMETHODCALLTYPE Open(D3D_INCLUDE_TYPE IncludeType, const char* pFileName, const void* pParentData, const void** ppData, UINT* pBytes) override{// 在这里实现你的文件打开逻辑// ...return S_OK;}virtual HRESULT STDMETHODCALLTYPE Close(const void* pData) override{// 在这里实现你的文件关闭逻辑// ...return S_OK;}
};// 使用自定义的包含逻辑编译着色器
ID3DBlob* compiledShader = nullptr;
HRESULT hr = D3DCompileFromFile(shaderFilePath, nullptr, nullptr, "VSMain", "vs_5_0", 0, 0, &compiledShader, nullptr, &myIncludeInstance);
在上面的示例中,MyInclude
类实现了 ID3DInclude
接口,并在构造函数中接受一个搜索路径参数。你可以根据需要在 Open
方法中实现自定义的文件搜索和打开逻辑。然后,在调用 D3DCompileFromFile
时,我们将 myIncludeInstance
(MyInclude
类的一个实例)作为参数传递,以便使用自定义的包含逻辑。
4、示例代码中的实现
在 GRSD3D12Sample/25-IBL-MultiInstance-Sphere 及系列示例中,具体实现这个接口如下:
#pragma once
#include <SDKDDKVer.h>
#include <tchar.h>
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料
#include <windows.h>
#include <strsafe.h>#include <atlconv.h>
#include <atlcoll.h>
#include <atlstr.h>
#include <d3dcompiler.h>#include "GRS_Mem.h"#define GRS_FILE_IS_EXIST(f) (INVALID_FILE_ATTRIBUTES != ::GetFileAttributes(f))#ifndef GRS_SAFE_CLOSEFILE
//安全关闭一个文件句柄
#define GRS_SAFE_CLOSEFILE(h) if(INVALID_HANDLE_VALUE != (h)){::CloseHandle(h);(h)=INVALID_HANDLE_VALUE;}
#endif // !GRS_SAFE_CLOSEFILE// 强制D3DCompiler搜索标准的Include目录,通常这也没啥用,只是保留记得有这么个设置即可
#ifndef D3D_COMPILE_STANDARD_FILE_INCLUDE
#define D3D_COMPILE_STANDARD_FILE_INCLUDE ((ID3DInclude*)(UINT_PTR)1)
#endiftypedef CAtlArray<CAtlString> CStringList;class CGRSD3DCompilerInclude final : public ID3DInclude
{
protected:CStringList m_DirList;
public:CGRSD3DCompilerInclude(LPCTSTR pszDir){AddDir(pszDir);}virtual ~CGRSD3DCompilerInclude(){}
public:VOID AddDir(LPCTSTR pszDir){CString strDir(pszDir);if ( ( strDir.GetLength() > 0 ) ){ //非空路径if (_T('\\') == strDir[strDir.GetLength() - 1]){strDir.SetAt(strDir.GetLength() - 1, _T('\0'));}for ( size_t i = 0; i < m_DirList.GetCount(); i++ ){if ( m_DirList[i] == strDir ){// 已经存在不用再添加了return;}}m_DirList.Add(strDir);}}
public:STDMETHOD(Open)(THIS_ D3D_INCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID* ppData, UINT* pBytes) override{HANDLE hFile = INVALID_HANDLE_VALUE;VOID* pFileData = nullptr;*ppData = nullptr;HRESULT _hrRet = S_OK;try{ CString strFileName(pFileName);TCHAR pszFullFileName[MAX_PATH] = {};for (size_t i = 0; i < m_DirList.GetCount(); i++){HRESULT hr = ::StringCchPrintf(pszFullFileName, MAX_PATH, _T("%s\\%s"), m_DirList[i].GetBuffer(), strFileName.GetBuffer() );if ( FAILED(hr) ){AtlThrow(hr);}if ( !GRS_FILE_IS_EXIST(pszFullFileName) ){// 记录下这个错误,直到最后真找不到那么就作为最后的错误码返回_hrRet = HRESULT_FROM_WIN32(::GetLastError());continue;}hFile = ::CreateFile(pszFullFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);if ( INVALID_HANDLE_VALUE == hFile ){AtlThrowLastWin32();}DWORD dwFileSize = ::GetFileSize(hFile, NULL);//Include文件一般不会超过4G大小,所以......if (INVALID_FILE_SIZE == dwFileSize){AtlThrowLastWin32();}VOID* pFileData = GRS_CALLOC(dwFileSize);if (nullptr == pFileData){AtlThrowLastWin32();}if (!::ReadFile(hFile, pFileData, dwFileSize, (LPDWORD)pBytes, NULL)){AtlThrowLastWin32();}GRS_SAFE_CLOSEFILE(hFile);*ppData = pFileData;_hrRet = S_OK;break; }}catch (CAtlException& e){GRS_SAFE_CLOSEFILE(hFile);GRS_SAFE_FREE(pFileData);_hrRet = e.m_hr;}GRS_SAFE_CLOSEFILE(hFile);return _hrRet;}STDMETHOD(Close)(THIS_ LPCVOID pData) override{GRS_FREE((VOID*)pData);return S_OK;}
};
上面代码中,只是加入了一个预编译文件可能路径的支持,即用一个字符串对象的链表来存储所有潜在路径,在需要打开头文件时,就顺序搜索这个字符串列表,直到找到指定的头文件为止。
在实际使用时,就是在程序开头处,将所有的包含路径都加入到该对象中,然后编译时直接使用即可。
// 0-2、得到当前的工作目录,方便我们使用相对路径来访问各种资源文件
{if (0 == ::GetModuleFileName(nullptr, g_pszAppPath, MAX_PATH)){GRS_THROW_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));}WCHAR* lastSlash = _tcsrchr(g_pszAppPath, _T('\\'));if (lastSlash){//删除Exe文件名*(lastSlash) = _T('\0');}lastSlash = _tcsrchr(g_pszAppPath, _T('\\'));if (lastSlash){//删除x64路径*(lastSlash) = _T('\0');}lastSlash = _tcsrchr(g_pszAppPath, _T('\\'));if (lastSlash){//删除Debug 或 Release路径*(lastSlash + 1) = _T('\0');}// Shader 路径::StringCchPrintf(g_pszShaderPath, MAX_PATH, _T("%s25-IBL-MultiInstance-Sphere\\Shader"), g_pszAppPath);// 资源路径::StringCchPrintf(g_pszAssetsPath, MAX_PATH, _T("%sAssets"), g_pszAppPath);
}// 0-3、准备编程Shader时处理包含文件的类及路径
TCHAR pszPublicShaderPath[MAX_PATH] = {};
::StringCchPrintf(pszPublicShaderPath, MAX_PATH, _T("%sShader"), g_pszAppPath);
CGRSD3DCompilerInclude grsD3DCompilerInclude(pszPublicShaderPath);
grsD3DCompilerInclude.AddDir(g_pszShaderPath);
需要编译shader时:
ComPtr<ID3DBlob> pIVSCode;
ComPtr<ID3DBlob> pIGSCode;
ComPtr<ID3DBlob> pIPSCode;
ComPtr<ID3DBlob> pIErrorMsg;::StringCchPrintf(pszShaderFileName, MAX_PATH, _T("%s\\GRS_1Times_GS_HDR_2_CubeMap_VS_GS.hlsl"), g_pszShaderPath);HRESULT hr = D3DCompileFromFile(pszShaderFileName, nullptr, &grsD3DCompilerInclude, "VSMain", "vs_5_0", nCompileFlags, 0, &pIVSCode, &pIErrorMsg);if (FAILED(hr))
{ATLTRACE("编译 Vertex Shader:\"%s\" 发生错误:%s\n", T2A(pszShaderFileName), pIErrorMsg ? pIErrorMsg->GetBufferPointer() : "无法读取文件!");GRS_THROW_IF_FAILED(hr);
}
pIErrorMsg.Reset();
这篇关于DirectX12(D3D12)基础教程(二十三) ——DirectShaderCompiler 头文件接口 ID3DInclude 的应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!