【UE源码分析】PSO相关类代码分析

温(mian)馨(ze)提(sheng)示(ming):

  1. 此文内容质量极差,仅作为个人学习过程记录
  2. 作者刚学习UE一个月,文章只是作为笔记,自己都不一定能看得懂
  3. 这个文档是接上文使用PSO后进引擎源码阅读尝试理解引擎内部发生了什么
  4. 写完这个文档的感受就是:为什么会有我这样的nt会想到从类开始读代码啊,梳理完发现自己的学习过程全是踩坑,有一半以上的类是完全没有必要理解里面干了什么,下次读源码还是从函数开始看
  • OpenGLShaderResources.h

FOpenGLShaderResourceTable

该函数调用自C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\RenderCore\Public\ShaderCore.h的FBaseShaderResourceTable结构体,他用来保存与着色器资源绑定相关的信息的。着色器需要访问各种资源,如纹理、缓冲区和采样器。这个结构体是用来追踪这些资源在资源表中的位置和状态的

 FOpenGLShaderResourceTable对其增加了纹理的索引。

FOpenGLShaderVarying

这个类保存了shader的变量,并且可以给出哈希索引

FOpenGLShaderBindings

这个结构体是信息binding,包括着色器资源绑定相关的信息,输出输入变量,以及buffer的信息等(C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\RenderCore\Public\CrossCompilerCommon.h)

FOpenGLCodeHeader

该结构体包含了上一个提及的结构体,以及下图的buffer信息

C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\RenderCore\Public\CrossCompilerCommon.h)

TOpenGLShader

这个类继承自模板类RHIResourceType,可以看到所有的模板父类都是FRefCountedObject,可能写成这样是为了未来的代码可拓展性。

这个FRefCountedObject就是计算这个shader被引用了多少次

(C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\Core\Public\Templates\RefCounting.h)

回到 TOpenGLShader,可以看到模板参数还有两个用来生成枚举,然后这个类还保存id,binding,还有一个插槽的数组,可以点进去看到其实就是一个unit8;同样包含了FUniformBufferCopyInfo的一个数组

FOpenGLComputeShader

可以看到里面有一个FOpenGLLinkedProgram的指针其他几个函数都是通过这个指针来访问和返回该类的成员

之前可以看到这里除了compute shader都有了

FOpenGLProgramKey

这个类只有包含了六个FSHAHash的数组

FSHAHash用于存储由 FSHA1 生成的 SHA 哈希值。

SHA(Secure Hash Algorithm)是一种常用的密码学哈希函数,用于生成数据的唯一标识符。在 Unreal Engine 中,FSHAHash 类被用于存储 SHA1 哈希值。

C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\Core\Public\Misc\SecureHash.h)

FOpenGLLinkedProgramConfiguration

这里的shaderinfo就是binding和id,并且定义一个取名shader的shaderinfo数组,数量为6,表示包含cs的情况,可以看到EShaderStage就是一个枚举,其中NUM_NON_COMPUTE_SHADER_STAGES为5,表示不包含cs的情况;然后这个类还包含一个FOpenGLProgramKey类,用于作为info的哈希列表

C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\RenderCore\Public\CrossCompilerCommon.h)

FOpenGLShaderParameterCache

这个类用来cache uniform参数的,他的第一个成员变量的参数是枚举,点进去可以看到这个枚举是为了对应hlsl的,第二个变量是结构体的数组GlobalUniformArraySize,结构体包含了一个向量的范围,函数可以追踪修改为最大的向量的范围(设置为dirty);PackedUniformsScratch是用来暂存的;GlobalUniformArraySize用来记录字节数

InitializeResources函数为uniforms数据分配和初始化了必要的内存资源,并设置了相关的状态;MarkAllDirty函数把所有uniform arrays设置为dirty;Set函数更新uniform缓冲区中的数据,并用标记动过的数据范围;CommitPackedGlobals和CommitPackedUniformBuffers函数将修改过的Uniform数据提交给OpenGL程序,以便在下一次渲染时使用新的数据。因为Uniform数据通常会在每一帧或每次渲染调用中改变;

FOpenGLProgramBinaryCache

单例类先从私有的变量和函数看起,这里结构体存储代码,两个函数对shader的代码压缩或解压缩

这里首先定义了FGLProgramBinaryFileCacheEntry的指针的数组,但是是最后所有的汇总;第二个私有变量是用一个map来做key和entry指针的对应。

定义了FGLShaderToPrograms的结构体,该结构体也是一个FGLProgramBinaryFileCacheEntry指针的数组,后面定义了一个map用哈希来作为key;PendingGLProgramCreateRequests是FGLProgramBinaryFileCacheEntry的指针的数组,注释写的是用于存储通过异步加载的程序,并且现在已经准备好在拥有GL上下文的线程上进行创建。

bShownLoadingScreen这个bool值经过全局搜索后发现并没有被用上,后面的枚举类EBinaryFileState是表示二进制文件的状态:

  • Uninitialized(未初始化):表示尚未建立二进制文件,不能对其进行读取或写入操作。
  • BuildingCacheFile(正在构建缓存文件):表示正在从PSO预编译着色器,并将它们存储在新的二进制缓存中。此时不应尝试读取缓存文件。
  • BuildingCacheFileWithMove(正在构建带有移动的缓存文件):表示正在从PSO预编译着色器,并将其存储在新的二进制缓存中,同时将与现有缓存匹配的着色器移动到新文件中。此时不应尝试读取缓存文件。
  • ValidCacheFile(有效的缓存文件):表示存在一个有效的缓存文件,可以用于读取操作。此时不应尝试写入缓存文件。

FPreviousGLProgramBinaryCacheInfo结构体,OldCacheFilename放cache文件名,ProgramToOldBinaryCacheMap映射表用于从旧缓存中查找和重用程序二进制数据。NumberOfOldEntriesReused记录有几个PSO被重用。

这里全是私有函数,第一个函数AppendProgramBinaryFile全局搜索后发现未被定义;第二个函数AppendProgramBinaryFileEofEntry是在二进制文件中添加一个特殊的记录

ScanProgramCacheFile函数,这个函数很长,图中只截一部分,它的目的是扫描一个二进制缓存文件,并建立所有程序的记录。

首先创建一个临时文件把文件的内容给move过来,然后创建一个reader来读取,reader创建好以后,判断标志以指示缓存文件是否应被使用,如果二进制文件大小是0,不会报错只会报警告,构建一个缓存条目列表FGLProgramBinaryFileCacheEntry,每个条目代表文件中的一个程序。每个条目包含程序的键、其二进制数据的大小以及数据在文件中的起始偏移量

将OpenGL程序的二进制数据加载到缓存中,并根据着色器是否已加载来决定如何处理这些数据。如果所有着色器都已加载,程序的二进制数据将被读取并设置为已加载状态;如果不是,程序的状态将被设置为已存储,等待后续处理。此外,还有对无效文件或不匹配GUID的处理逻辑,以及更新文件读取指针的操作。

后续部分负责在文件扫描结束后的清理工作,包括记录日志信息、关闭文件阅读器、重命名文件以及处理不完整文件的情况。如果文件有效并且GUID匹配,它会将临时文件重命名回原来的文件名。如果文件无效或GUID不匹配,它会保留文件阅读器打开,以便将旧缓存中的着色器移动到新缓存中。如果文件不完整,它会关闭文件阅读器并记录警告信息,表示需要重建缓存。

这里处理二进制文件的有效性检查和后续的操作。如果文件无效,它会记录日志信息,并尝试清理和重新创建缓存目录。如果文件有效并且GUID匹配,它会准备异步读取操作并更新文件状态。如果无法打开缓存文件或无法创建缓存目录,它会记录相应的日志信息。

最后段代码负责在二进制文件无效或GUID不匹配时尝试创建新的缓存文件,并保存必要的文件头信息。如果无法创建新文件,它会记录错误并执行紧急处理流程

AddProgramFileEntryToMap函数的作用是将一个新的OpenGL程序二进制缓存条目添加到运行时查找容器中,可以在OpenGL程序二进制缓存系统中注册新的着色器程序,以便可以通过着色器哈希快速查找到对应的程序缓存条目。

OpenAsyncReadHandle函数的作用是打开一个新的异步读取句柄,准备进行文件读取操作CloseAsyncReadHandle函数已经被删除,但是代码还在可以推测他的功能,它创建一个作用域锁FScopeLock Lock(&GPendingGLProgramCreateRequestsCS);,这个锁用于同步对PendingGLProgramCreateRequests列表的访问,确保在等待读取操作完成时,没有其他线程修改列表。遍历PendingGLProgramCreateRequests列表中的FGLProgramBinaryFileCacheEntry对象。对于列表中的每个条目尝试获取与该条目关联的ReadRequest的TSharedPtr智能指针。

如果这个智能指针是有效的(即异步读取请求存在),则调用ReadRequest->WaitCompletion();来等待异步读取操作完成。完成后,将条目的ReadRequest成员设置为nullptr,表示没有正在进行的异步读取。最后删除BinaryCacheAsyncReadFileHandle指向的对象,并将其设置为nullptr,清理资源并标记异步读取句柄已关闭。

OpenWriteHandle函数获取程序二进制缓存文件的路径,并为写入操作创建一个临时文件名(在原有文件名后加上.write后缀)CloseWriteHandle函数负责关闭写入句柄,并确保所有数据都已正确写入文件,然后将临时文件重命名为最终文件名,完成缓存文件的创建过程

AppendGLProgramToBinaryCache函数确保新创建的OpenGL程序能够被缓存,无论是写入到文件中以便下次启动时使用,还是存储在实时缓存中以便当前运行时使用。这样可以提高程序的加载速度,因为在后续的运行中可以直接使用缓存的二进制数据,而不需要重新编译着色器源代码。AddUniqueGLProgramToBinaryCache确保每个OpenGL程序只被添加到二进制缓存一次。它通过检查程序是否已经存在于运行时映射中来避免重复添加,并在确认程序尚未缓存的情况下,从OpenGL程序对象中获取二进制数据并将其写入到缓存文件中。这有助于优化程序的加载时间,因为缓存的程序可以在后续的运行中被快速加载,而无需重新编译AddProgramBinaryDataToBinaryCache函数是将OpenGL程序的二进制数据写入到文件中,并更新运行时的数据结构,以便程序可以在之后被快速地加载和使用

UseCachedProgram_internal函数尽可能地使用缓存中的OpenGL程序,以提高性能。它首先尝试从当前缓存中获取程序,如果不成功,它会尝试从旧缓存中恢复程序。如果从旧缓存中成功恢复了程序,它会将这个程序添加到新缓存中,并更新相关的统计信息。

OnShaderLibraryRequestShaderCode_internal函数的目的是在着色器代码被请求时,检查是否可以从缓存中加载对应的OpenGL程序。可以的话会启动读取程序的过程

BeginProgramReadRequest函数的目的是启动一个异步读取请求,用于从缓存中加载OpenGL程序的二进制数据。如果无法进行异步加载,它会回退到同步加载

CheckPendingGLProgramCreateRequests_internal()函数的目的是在有限的时间内尽可能多地处理挂起的OpenGL程序创建请求。它会记录处理的开始时间和处理的数量,并确保在超过最大处理时间后停止处理新的请求。这有助于平衡程序创建的工作量,避免在单个帧中花费太多时间处理程序创建请求CheckSinglePendingGLProgramCreateRequest_internal函数只处理与特定ProgramKey关联的单个请求。它不受时间限制,会等待异步读取完成,并确保这个特定的请求被处理

CompleteLoadedGLProgramRequest_internal函数的主要作用是根据加载的二进制数据创建OpenGL程序,并根据缓存策略将程序添加到缓存中或丢弃不需要的数据。

OnShaderPipelineCacheOpened在缓存文件打开时被调用,用于扫描和准备预编译过程。OnShaderPipelineCachePrecompilationComplete在预编译完成时被调用,用于清理和设置后续的读取操作。

  • OpenGLShaders.cpp

FOpenGLLinkedProgram

第一个类FOpenGLLinkedProgramConfiguration包含了link的初始信息,但那个类的信息不够,在下图的地方FPackedUniformInfo 结构体包含了每个打包的uniform变量的信息。FStagePackedUniformInfo 结构体包含了每个渲染阶段需要的打包uniform信息。PackedUniformInfos 是一个数组,包含了每个打包的uniform变量的信息。PackedUniformBufferInfos 是一个二维数组,外层数组是每个Uniform Buffer,内层数组是每个precision/type的信息。LastEmulatedUniformBufferSet 是一个数组,包含了最后一个上传到程序的uniform缓冲区的唯一ID。

StagePackedUniformInfo 是一个数组,包含了每个渲染阶段的打包uniform信息。数组的大小是 CrossCompiler::NUM_SHADER_STAGES,这表示渲染管线中的着色器阶段数量。

这个数据结构是用于管理和跟踪OpenGL着色器中的uniform变量和uniform缓冲区的。

然后后面就是一些小的参数了,有一个 TArray<FOpenGLBindlessSamplerInfo> Samplers;可以看到保存了纹理的编号和采样器插槽的编号

C:\Unreal4.27\UnrealEngineEX_4.27.2-release\Engine\Source\Runtime\OpenGLDrv\Public\OpenGLShaderResources.h

类中有个类是FLRUInfo,用于管理最近最少使用(LRU)缓存的信息。LRUNode是一个FSetElementId类型的对象,它在LRU缓存中表示一个特定的节点。当需要更新LRU状态时,可以通过这个ID快速访问对应的节点。CachedProgramBinary:这是一个TArray<uint8>类型的数组,用于存储缓存的程序二进制数据。可能是一个已编译的着色器程序。EvictBucket是一个int32类型的变量,用于表示当前节点是否正在等待被驱逐出缓存。如果EvictBucket的值小于0,那么表示这个节点不在驱逐列表中。如果EvictBucket的值大于等于0,那么表示这个节点正在等待被驱逐,值本身表示的是驱逐列表的索引。LRU缓存是一种常用的缓存策略,它会优先驱逐最长时间未被使用的数据,以此来保证缓存中的数据都是最近被使用的。

里面的FSetElementId这个类FSetElementId是一个用于表示集合元素的标识符。它主要用于TSet和TScriptSet这两种集合类型

后面这里有四个函数,

第一个函数VerifyUniformBlockBindings,在更改可分离着色器管线时会重新绑定uniform,因对于平常的的 OpenGL 不执行任何操作。

所以可以看到在我们OpenGL3的条件下,if判断的第一个参数是一个为false的bool值,这段代码不会执行,但是还是分析下这个功能,这个函数确保所有的Uniform Block都正确地绑定到了预期的位置。如果有任何错误,那么shader程序可能无法正确地访问Uniform Buffer中的数据,从而导致渲染错误

第二个函数是为了让OpenGL能实现类似UAV的东西,应该是通过ubo创建的

后面这个函数的作用是将从着色器反射得到的uniform信息进行匹配和排序,然后存储到输出数组中,三个参数为(const TArray<FPackedUniformInfo>& ReflectedUniformInfos, const TArray<CrossCompiler::FPackedArrayInfo>& PackedGlobalArrays, TArray<FPackedUniformInfo>& OutPackedUniformInfos);最后一个函数只有两行,表示初始化完成

FGLProgramCacheLRU

这个类里面定义了一个内部类FEvictedGLProgram,其实就是封装了函数的FOpenGLLinkedProgram,保存被剔除的二进制文件并且可以重新装回来;类本身包含的是一个FOpenGLProgramLRUCache和FOpenGLEvictedProgramsMap,定义如下:

FGLProgramCache

这个类包括FGLProgramCacheLRU和FOpenGLProgramsMap,通过bool值判断是否支持lru,支持就用前者,不支持就用后者

FGLProgramBinaryFileCacheEntry

这个类包含缓存文件中单个程序条目的运行时和文件信息;第一个结构体FGLProgramBinaryFileCacheFileInfo包含了用于在二进制缓存文件中定位程序的位置信息,枚举类EGLProgramState表示OpenGL程序在不同阶段的状态,从未设置到加载完成。

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值