DirectX12(D3D12)基础教程(二十三) ——DirectShaderCompiler 头文件接口 ID3DInclude 的应用

本文主要是介绍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 头文件中,并且通常与 D3DCompileD3DCompileFromFile 函数一起使用,这些函数是用于编译着色器的主要函数。

  下面是 ID3DInclude 接口中定义的主要方法:

  • Open:当编译器遇到 #include 指令时,它会调用此方法。应用程序应该实现此方法以打开指定的文件,并返回一个指向文件内容的指针。

  • Close:在编译器完成处理一个包含的文件后,它会调用此方法。应用程序应该实现此方法以关闭文件并释放任何关联的资源。

      需要注意的是,仅包含这两个函数的版本是用于D3D12配套版本的着色器编译器的。

3、基本使用方法

  为了使用自定义的包含逻辑,你需要实现一个类,该类继承自 ID3DInclude 接口,并实现上述所有方法。然后,在调用 D3DCompileD3DCompileFromFile 时,你可以将你的实现作为参数传递。

  下面是一个简单的示例,展示了如何实现 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 时,我们将 myIncludeInstanceMyInclude 类的一个实例)作为参数传递,以便使用自定义的包含逻辑。

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 的应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/931975

相关文章

Java Stream 的 Collectors.toMap高级应用与最佳实践

《JavaStream的Collectors.toMap高级应用与最佳实践》文章讲解JavaStreamAPI中Collectors.toMap的使用,涵盖基础语法、键冲突处理、自定义Map... 目录一、基础用法回顾二、处理键冲突三、自定义 Map 实现类型四、处理 null 值五、复杂值类型转换六、处理

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到