Unity6 + UE5.4 PSO缓存实践记录

news/2024/9/17 7:43:13/ 标签: ue5

题图(取自COD冷战的着色器编译提示)

PSO(管线状态对象 Pipeline State Object)是伴随现代图形API(DirectX12、Vulkan、Metal)而出现的概念,它本质上是单次绘制时渲染管线所处的状态信息的集合(Shader、混合器状态、光栅器状态、图元拓扑信息等)。在D3D12中,PSO的描述信息由D3D12_GRAPHICS_PIPELINE_STATE_DESC结构体给出:

typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC {ID3D12RootSignature                *pRootSignature;D3D12_SHADER_BYTECODE              VS;D3D12_SHADER_BYTECODE              PS;D3D12_SHADER_BYTECODE              DS;D3D12_SHADER_BYTECODE              HS;D3D12_SHADER_BYTECODE              GS;D3D12_STREAM_OUTPUT_DESC           StreamOutput;D3D12_BLEND_DESC                   BlendState;UINT                               SampleMask;D3D12_RASTERIZER_DESC              RasterizerState;D3D12_DEPTH_STENCIL_DESC           DepthStencilState;D3D12_INPUT_LAYOUT_DESC            InputLayout;D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;D3D12_PRIMITIVE_TOPOLOGY_TYPE      PrimitiveTopologyType;UINT                               NumRenderTargets;DXGI_FORMAT                        RTVFormats[8];DXGI_FORMAT                        DSVFormat;DXGI_SAMPLE_DESC                   SampleDesc;UINT                               NodeMask;D3D12_CACHED_PIPELINE_STATE        CachedPSO;D3D12_PIPELINE_STATE_FLAGS         Flags;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;

Vulkan和Metal中,有和PSO相对应的概念,它们并不叫PSO,但是在提到现代图形API的图形管线状态时,我们通常都用PSO来指代


旧式图形API(如OpenGL、DirectX11等)中,管线状态可通过单独的API Call来在渲染过程中进行设置(比如glSetBlendState、glCompileShader等),这带来了几个潜在问题:

  • 在实际的DrawCall发出之前,用户可以随意改变管线的状态,所以图形驱动通常需要额外跟踪状态信息,并将实际的状态设置工作延迟到实际的DrawCall之前执行
  • 图形驱动需要在渲染过程中实时计算硬件状态,将API编码到硬件指令
  • 游玩时编译着色器会造成卡顿(虽然游戏引擎会提供一些预热功能来规避)
  • 这种工作流程本质上将管线状态设置工作耦合进渲染循环中,会干扰渲染效率

新式图形API通过引入PSO的概念,将管线状态视为一个集合对象,使用PSO之后的管线有以下优点

  • 管线状态不再可以在渲染过程中被松散设置
  • 一旦PSO对象被创建,其立刻处于可用的状态,且管线编译期信息不再可以被更改(如着色器、混合状态、颜色掩码等)
  • 着色器的编译相当于在PSO创建时就完成了
  • 因为PSO创建后的编译期相关信息不可以再更改,因而大部分硬件指令已经在此时被生成,驱动程序不需要再操心地跟踪管线状态
  • 渲染过程中,只需切换不同的PSO就可以改变管线状态,切换PSO的过程是相对较快的,这样可以将管线状态设置工作从渲染循环中剥离出来

概括来说,PSO设计理念之一,就是要将渲染过程中存在的实时状态计算(包括着色器编译),都显式地转移到一个统一的时间段内一气呵成地做完,将编译内容和渲染过程解耦,以为渲染过程让出更多的时间


但PSO的创建(编译)是一个非常非常耗时的操作,其时间开销已经不能和传统API的运行时着色器编译相提并论,即便是一些小Demo级的游戏里,如果尝试在游玩时创建关卡内所有物体的PSO,也可能导致长达数秒的停滞

现代游戏引擎为解决这个问题,通常会针对现代图形API平台增加PSO缓存支持,PSO缓存系统简单来说是帮助开发人员生成当前应用程序用到的所有PSO的列表,以便告诉用户机器在合适的阶段(比如加载期间,或由开发人员指定的其它时间)编译好这些PSO,这样能够避免游玩时创建所带来的停滞


这几年越来越多的PC平台游戏转向D3D12/Vulkan等新API,因此预先创建PSO成为了迫在眉睫的需要,大家可能发现这两年越来越多的游戏会显式的提示用户等待着色器编译(题图)

听起来是不是挺像Shader变体收集的,其实他们只有亿点点差别,如果你并不熟悉PSO,那你可以非常粗略的认为PSO缓存收集和编译就是新式图形API下的Shader变体收集和编译(但它们在底层的工作细节差别巨大)

本文主要内容

  1. 基于UE官方的游戏Demo来测试Unreal 5.4中的PSO缓存功能
  2. StackOBot,比较简单,有一个3D欢迎界面和一个关卡

  3. 古代山谷,相对更复杂,有两个自由移动关卡,带战斗和运行时生成的特效

  4. 彼时Unity6也已经推出了PSO缓存的功能,我们会利用新的URP模板场景来进行测试

UE PSO缓存实践

概览

Unreal 5.4中,PSO系统有两个组成部分:一个是PSO Precache,另一个是Bundle PSO Cache

PSO Precache(PSO预缓存系统):

是一种相对自动的预编译PSO的功能,相关逻辑在Component的**PostLoad()**函数内被调用。当用户第一次加载关卡的时候,Precache系统会尝试收集关卡内对象的网格和材质信息,并在后台线程上异步编译PSO

由于编译PSO的过程发生在关卡加载阶段,因此游玩时不会因为PSO编译而导致游戏卡顿(当然,理想情况如此);且编译的内容会被缓存下来,当用户第二次进入同一个关卡时,通过复用第一次的结果,加载速度会更快

Precache系统本质上依赖于底层缓存,编译好的数据只适用于当前软硬件环境。当图形驱动版本更换、图形硬件更换后,PSO需要被重新生成(这也就是你换了显卡或者更了驱动之后,游戏会提示你重新编译着色器的原因)

Precache系统相当于是分步进行的,因为它只会在加载时生成本关卡的PSO,这代表着玩家不需要在一上来就编译整个游戏要用到的所有PSO

另外,Precache的过程在PostLoad期间进行,但运行时生成的Actor并不会调用该函数,这意味着运行时由用户生成的新Actor的PSO可能无法被记录在案(比如一个只有在玩家触发时才出现的粒子特效,且该粒子特效并不是一开始就在关卡内的),这部分PSO会在游玩过程中编译,造成卡顿

该功能在5.3中首次出现,于5.4中默认开启,目前仅支持D3D12平台,移动端滚出克

Bundle PSO Cache(打包PSO缓存):

用于手动收集PSO,该系统早在UE4时就有了,开发人员需要手动跑游戏来收集PSO列表,并将收集到的文件包含到包体内发布给用户;当用户首次启动游戏时,Bundle PSO就开始编译

由于其流程依赖于手动采集,所以目前Epic也不是很青睐这种做法,官方是想在未来将PSO收集的过程完全自动化,以Precache系统完全替代Bundle PSO

但实际上目前最佳的实践仍是将这二者结合使用,因为PSO Precache总会有漏掉的地方,做不到100%的覆盖率

StackOBot项目简介

该项目刚启动时就进入一个3D场景+UI做成的欢迎界面:

因此实际上很多PSO编译的过程在刚进游戏就开始了,这样我们来不及启动Insight抓取数据

为了能更好的分析该3D欢迎界面的PSO编译情况,我又给它套娃了一层界面,让用户点两次启动才能进游戏

创建空关卡,并给一个UI,点击按钮后跳转到3D欢迎界面

1. StackOBot,关闭PSO缓存系统,清除上一次缓存

测试环境:13700F+RTX4070

第一次测试,我们使用下列命令关闭PSO预缓存系统,而且不会使用Bundle PSO

[/Script/Engine.RendererSettings]
r.PSOPrecaching=0

这样生成的包体内不含任何Bundle PSO信息,且加载关卡的时候也不会提前准备PSO,所有PSO都需要在用户游玩过程中遇到的时候生成

打包游戏,并使用 -clearPSODriverCache 启动参数来运行游戏,因为第一次编译PSO后的结果会被缓存下来,但我们是为了测试PSO缓存覆盖率,所以要保证每一次启动游戏时,上一次生成的二进制数据都要被清空,否则会干扰判断

1号尖峰:

RHICreateComputePipelineState(计算PSO):4.8s
RHICreateGraphicsPipelineState(图形PSO):1.7s

2号尖峰是一组连续尖峰的集合,其中最长的一个:

RHICreateComputePipelineState(计算PSO):1.0s
RHICreateGraphicsPipelineState(图形PSO):1.9s

游玩过程的小尖峰(出现尖峰时玩家有明显卡顿感):

RHICreateComputePipelineState(计算PSO):未触发
RHICreateGraphicsPipelineState(图形PSO):39.7ms -> 68.7ms -> 92.8ms

结论:

  • 运行时PSO创建开销很大,会造成明显的感知停顿

这还只是个小Demo场景,如果是更复杂的,卡顿会进一步放大导致几乎不能玩;另外,用户机器的CPU不一定比测试机好,所以他们的编译时间可能要更长

如果测试机的CPU核心数非常多,这可能会弱化PSO编译带来的停滞,此时可以使用 -corelimit=n (n是需要输入的核心数量)启动参数来运行游戏,可以更准确的复现目标机器编译PSO的耗时

2. StackOBot,关闭PSO缓存,不清除上一次缓存

这次我们不清除上一次的编译缓存,再来运行一下游戏

1号尖峰:

RHICreateComputePipelineState(计算PSO):414.7ms
RHICreateGraphicsPipelineState(图形PSO):162.7ms

2号尖峰:

RHICreateComputePipelineState(计算PSO):74.6ms
RHICreateGraphicsPipelineState(图形PSO):188.3ms

结论:

进游戏时PSO创建过程用时变短了,且游玩过程没有PSO编译尖峰,这受益于上一次运行时所保留下来的数据,此时所谓的PSO编译只是加载上一次的结果

注意,因为这一次的PSO创建依赖于上一次的缓存,假设上一次没走到某个特殊点A,那么这一次走到A点可能还是会触发编译尖峰,等到下次启动游戏时才会加载缓存

3. StackOBot,只启用PSO Precache,清除上一次缓存

首先启用PSO Precache系统

[/Script/Engine.RendererSettings]
r.PSOPrecaching=1

(可选)启用PSO Precache验证层,0表示关闭;1表示轻量级跟踪,对性能影响小;2表示详细跟踪;启用验证层可以在运行游戏时直观的看到PSO预缓存的命中情况

我们会先使用轻量级跟踪,后面会描述轻量级跟踪和详细跟踪的区别

[/Script/Engine.RendererSettings]
r.PSOPrecaching=1
r.PSOPrecache.Validation=1

1号尖峰:

RHICreateComputePipelineState(计算PSO):4.4s
RHICreateGraphicsPipelineState(图形PSO):3.6s

2号尖峰:

RHICreateComputePipelineState(计算PSO):1.2s
RHICreateGraphicsPipelineState(图形PSO):4.6s

对比第一次测试(第3小节)可知,本次2号尖峰(也就是正式进入游戏时)的PSO创建时长比之前高出许多,尤其是图形PSO部分

这代表关卡中要用到的大部分PSO都已经在关卡加载的时候被创建,因此游玩过程中的PSO尖峰就几乎不存在了


如果启用了PSO Precache验证层,那么跑游戏时可以通过Stat PSOPrecache命令来查看PSO缓存命中情况

图中显示有17个未追踪的PSO缓存,官方文档里给出未追踪的可能原因是

验证被禁用、全局材质、VertexFactory不受支持、MeshPassProcessor类型不受支持

这里我尝试将验证级别给到2,重新打包,其它参数不变,得到如下结果:

可以看到大部分PSO成功命中,少部分PSO在使用时还没来得及编译完毕(Too Late Count)

要注意的是该面板只统计“应该被Precache系统所捕获的PSO”,如果是Precache本身无法捕获到的内容(比如运行时生成的内容),是不会出现在这里的

4. StackOBot,只启用PSO Precache,清除上一次缓存,运行时生成Actor

先前提到过,Precache系统会在组件的PostLoad函数调用期间来编译PSO,而运行时生成的Actor不调用PostLoad,这会导致Precache无法准确收集到PSO

而且要额外注意两个问题:

  1. 只有当【运行时生成的新Actor所携带的PSO】与【当前加载的关卡内的任一物件的PSO】都不匹配的时候,才会触发运行时编译,否则它只是复用已编译的PSO
  1. Precache调试层不能捕获运行时Actor的PSO Miss事件,因为这种Actor本身就不归Precache管,所以不要觉得Precache Miss Count为0就是100% PSO覆盖

这里我做了一个简单测试,新建一个带Trigger的Actor,当玩家触发时会生成一个小蓝人,这个小蓝人的PSO信息从未在当前关卡出现过


Insight数据如下:

可以看到,触发Trigger的时候出现了81.9ms的图形PSO编译;另外,触发Trigger之前有一个很矮的小尖峰,我看了一下是20ms的计算PSO编译

但是第3小节测试的时候没发现有这个问题,所以也挺迷惑的,不知道是动了什么


不过,这些数据终究还是表明Precache系统做不到100%的PSO覆盖率,实际我们还是要结合Bundle PSO来使用,在接下来的古代山谷项目里,我们会尝试使用二者结合的工作流

5. 古代山谷项目,使用PSO Precache,清除上一次缓存

Insight数据如下:

1号尖峰是一系列尖峰的集合,只记录最高的数据:

RHICreateComputePipelineState(计算PSO):7.7s
RHICreateGraphicsPipelineState(图形PSO):5.5s

运行时PSO编译:

RHICreateComputePipelineState(计算PSO):未触发
RHICreateGraphicsPipelineState(图形PSO):32.8ms -> 15.2ms -> 296.7μs

从Insight数据看,我们确实触发了相当长时间的运行时PSO编译,但通过PSO Precache验证数据的结果来看,Miss Count始终保持为0,所以再次强调不要通过这个面板验证整体PSO的覆盖率

下面我们就尝试使用Bundle PSO Cache,来将这部分游玩时PSO编译内容给优化掉

7. 古代山谷项目,PSO Precache + Bundle PSO Cache,清除驱动缓存

① 配置设置:
  1. 在DefaultEngine.[ini](https://zhida.zhihu.com/search?q=ini&zhida_source=entity&is_preview=1)或者(Platform)Engine.ini中添加(实际项目更推荐后者,因为不同平台的PSO需要不同的收集):
[DevOptions.Shaders]
NeedsShaderStableKeys=true[/Script/Engine.RendererSettings]
r.ShaderPipelineCache.Enabled=1
r.ShaderPipelineCache.ExcludePrecachePSO=1

其中,[r.ShaderPipelineCache.ExcludePrecachePSO=1](https://zhida.zhihu.com/search?q=r.ShaderPipelineCache.ExcludePrecachePSO%3D1&zhida_source=entity&is_preview=1) 用于确保Bundle PSO不会收集PreCache已经收集过的PSO

2. 在DefaultGame.ini中添加(注意该ini文件中可能已经存在该配置,记得先查找一下看看):

[/Script/UnrealEd.ProjectPackagingSettings]
bShareMaterialShaderCode=True
bSharedMaterialNativeLibraries=True 

3. 如果你从来没用过Bundle PSO Cache,请确保(Project)/Build/(Platform)/PipelineCaches文件夹是空的或根本不存在


② 跑游戏收集PSO:
  • 打包项目
  • 为打包出的程序添加-[logPSO](https://zhida.zhihu.com/search?q=logPSO&zhida_source=entity&is_preview=1) 启动参数,然后运行游戏
  • 尽可能覆盖所有位置

③ 转换PSO缓存:
  1. 跑完游戏后,你会发现游戏打包的目录中的(GameName)/Saved/CollectedPSOs里已经有了一个后缀为.rec.upipelinecache的文件,将该文件复制到一个自定义路径下(比如D:\PSOCache,你可以选择自己喜欢的,无所谓)
  2. 回到项目路径,找到(Project)/Saved/Cooked/(Platform)/(ProjectName)/Metadata/PipelineCaches文件夹,拷贝里面所有的.shk文件,复制到D:\PSOCache中(和.rec.upipelinecache一个目录)

有些时候,因项目平台设置的关系,.shk文件可能有PCD3D_SM5和PCD3D_SM6两种,实测如果把这两种SM的文件都放到D:\PSOCache下,会导致之后使用转换PSO缓存打包时报错,建议一次只复制一个SM级别的.shk

3. 在 D:\PSOCache 中新建一个txt,输入以下内容后改名为PSOCache.bat:

F:\Programme\Unreal\UE_5.4\Engine\Binaries\Win64\UnrealEditor-Cmd.exe -run=ShaderPipelineCacheTools expand D:\PSOCache\*.rec.upipelinecache D:\PSOCache\*.shk D:\PSOCache\20240819_AncientVallery_PCD3D_SM6.spc

  • 这里第一行是UnrealEditor-Cmd.exe的路径,这个是跟着你UE安装目录走的
  • 第二行注意要把 D:\PSOCache 改成你自己自定义的路径,如果你也是和我一样的路径,就无所谓
  • 第三行是最终生成的.spc文件名,命名随意

4. 运行该bat,你应该能得到一个.spc文件:


④ 使用转换后的PSO缓存再次打包:
  • 将③中生成的.spc文件复制到项目路径(Project)/Build/(Platform)/PipelineCaches中,再次打包
  • 注意一下打包过程中的log,看下有没有LogShaderPipelineCacheTools:开头的,有的话就正常


⑤ 运行游戏:
  1. 默认情况下,Bundle PSO会在游戏刚启动的时候就开始编译,你可以添加-Log启动参数,然后运行游戏看一下日志,如果有出现 LogRHI : FShaderPipelineCache::BeginNextPrecompileCacheTask(),则代表触发了编译过程

⑥ Insight数据:

1号尖峰:

RHICreateComputePipelineState(计算PSO):2.4s
RHICreateGraphicsPipelineState(图形PSO):11.1s

2号尖峰:

RHICreateComputePipelineState(计算PSO):1.8s
RHICreateGraphicsPipelineState(图形PSO):11.4s

游玩时PSO编译:未触发,但确实有尖峰,不过这就不在本文的范畴里了;由此可见我们成功通过Bundle PSO Cache弥补了Precache的不足

Unity6的PSO缓存

关于Unity6的PSO缓存,社区上有这么一个帖子:

https://discussions.unity.com/t/graphicsstatecollection-tracing-and-warmup-in-unity-6/951031​discussions.unity.com/t/graphicsstatecollection-tracing-and-warmup-in-unity-6/951031

简而言之:

  • Unity6中的PSO缓存类似UE的Bundle PSO Cache,需要手动收集,并在合适的时机预热
  • Unity可能会在未来版本中推出类似PSO Precache的功能(咕)
  • 传统的 ShaderVariantCollection.WarmUp 并不能适用于新图形API,因为该方法不能提供PSO所需的一些特殊信息,在新API下需要使用 GraphicsStateCollection
  • GraphicsStateCollection 目前在传统API下没有Fallback(这意味着在旧平台上要手动回退到_ShaderVariantCollection_.WarmUp?)

1. 新URP场景,关闭PSO缓存

我们使用新URP场景模板进行测试,该场景打包出来后是一个自动运行的Benchmark程序,用户无法以第一人称视角操作,如果你想自由移动,可以在打包的时候去掉第一个场景

该场景有三个展览室,进去会触发动画,同时也会触发PSO编译

在不使用任何预编译PSO的情况下,得到的Profiler数据如下,在进入展览室的时候,有一个142.43ms的尖峰,玩家卡顿感非常明显:

可以看到该帧的生成时间很长,若干个线程内都有PSO编译事件,我这里最猛的是2号线程,连续的几个PSO编译事件堆积起来可以有50多ms

2. 新URP场景,启用PSO缓存

① 创建脚本:

新建一个Mono脚本,编写代码,这里我写的是最基本的:

public class PSOCache : MonoBehaviour
{private GraphicsStateCollection _graphicsStateCollection;private string _cacheFilePath;private bool _hasValidCache = false;void Start(){_cacheFilePath = Application.dataPath + "/PSOCache.graphicsstate";_graphicsStateCollection = new GraphicsStateCollection();_hasValidCache = _graphicsStateCollection.LoadFromFile(_cacheFilePath);if (_hasValidCache){_graphicsStateCollection.WarmUp();}else{_graphicsStateCollection.BeginTrace();}}private void OnDestroy(){if (!_hasValidCache){_graphicsStateCollection.EndTrace();_graphicsStateCollection.SaveToFile(_cacheFilePath);}}
}

② 跑游戏收集PSO:

打包后,跑游戏,尽可能多的覆盖,这一步本质上和以前跑变体收集没什么区别,实际项目可以做成自动化的

结束游戏后,DataPath下会出现一个pso缓存文件:


③ 再次运行游戏查看Profiler数据:

虽然存在一些尖峰(最高的约28ms),但这些尖峰都不是PSO编译导致的,所以就不讨论了

如果你的PSO Cache命中良好,那么游玩过程中是不应该出现CreateGraphicsPipelineImpl事件的

补充说明

unity官方发的帖子,在跑GardenScene场景的时候,最多能干到276ms,Worker内的单个PSO编译事件能跑到84ms,但我本地测试没有这么恐怖,单个Worker内的单个PSO编译事件只占8ms左右(然后积少成多)

不知道是不是我的操作有问题,如果有dalao跑过相关流程请指教

Q & A

PSO的话题下常出现这样几个常见疑问:

Q:PSO能不能让厂商编译好,直接给用户,这样用户就不用等待编译了

A:固定硬件(Xbox,PS5,SteamDeck等)平台可以,PC不行,图形驱动和图形硬件都不一样,机器码都需要重编

Q:为什么以前的游戏没有着色器编译过程?

A:也有,只是没弹窗让你看到而已,现在这些游戏东西太多了,而且在新API下,如果不提前编,进游戏时实时创建PSO可比以前运行时编Shader开销大多了。所以目前的游戏都倾向于告(恐)诉(吓)用户最好编完再进,不然电脑会爆炸

附录

UE5.4 PSO预缓存源码调用链

这里简单描述一下,或许能帮助大家理一下思路

结构:[文件名] ~ [命名空间+函数名] [关键行数]

① RHI层:

Material.cpp ~ UMaterial::PrecachePSOs 2641 (注意PrecachePSOs函数在Component中也存在) ->

MaterialShared.cpp ~ FMaterial::CollectPSOs 2947 ->

PSOPrecache.cpp (Extern) ~ PrecacheMaterialPSOs ->

PSOPrecache.cpp ~ FMaterialPSORequestManager::PrecachePSOs 327 (如果先前已缓存过,则会于283行返回) ->

MaterialShared.cpp ~ FMaterialShaderMap::CollectPSOs 2744 ->

PSOPrecache.cpp (Extern) ~ PrecachePSOs 135 & 148 ->

PipelineStateCache.cpp ~ PipelineStateCache::PrecacheGraphicsPipelineState ->

PipelineStateCache.cpp ~ FPrecacheGraphicsPipelineCache::PrecacheGraphicsPipelineState 2775 ->

PipelineStateCache.cpp ~ TryAddNewState

1087行调用[CreateNewPSO](https://zhida.zhihu.com/search?q=CreateNewPSO&zhida_source=entity&is_preview=1)来初始化内存,但没有实际创建内容

1107行调用OnNewPipelineStateCreated来实际创建PSO ->

PipelineStateCache.cpp ~ FPrecacheGraphicsPipelineCache::OnNewPipelineStateCreated 2790 ->

PipelineStateCache.cpp ~ InternalCreateGraphicsPipelineState

如果是异步创建,则通过2524行的CompilePSO函数来创建

如果非异步创建,则直接调用2541行的RHICreateGraphicsPipelineState函数来创建 ->


② D3D12平台层:

D3D12State.cpp ~ FD3D12DynamicRHI::RHICreateGraphicsPipelineState ->

获取FD3D12PipelineStateCache的引用,并尝试在其中查找给定的PSO描述,如果找得到则直接返回

如果是新PSO则调用CreateAndAdd函数创建新PSO

D3D12RHIPrivate.h给出了D3D12平台下的具体行为


③ 传统API层(以D3D11为例):

D3D11平台无应用层PSO概念,因此没有重写RHICreateGraphicsPipelineState函数,D3D11RHIPrivate.h给出了D3D11平台下的具体行为

因为没有PSO概念,所以在Set Pipeline State的时候,D3D11走的是IRHICommandContextPSOFallback接口下的RHISetGraphicsPipelineState函数;该接口专门为不支持应用层PSO的图形API提供管线状态设置方法

Reference

  • Tomlooman关于UE PSO Cache的文章
  • 现代图形API的缺省指南-PSOs
  • UE5.4: PSO预缓存
  • UE Actor生命周期 - PostLoad函数
  • Unity 6 中的 GraphicsStateCollection 跟踪和预热

http://www.ppmy.cn/news/1521878.html

相关文章

Java实用类——操作日期和时间

Date类: Date 位于java.util.Date类:表示日期和时间SimpleDateFormat 位于java.text.SimpleDateFormat类:格式化日期类parse方法:将字符串转为日期类型 public static void main(String[] args) throws ParseException {//获得当…

Unity Adressables 使用说明(五)在运行时使用 Addressables(Use Addressables at Runtime)

一旦你将 Addressable assets 组织到 groups 并构建到 AssetBundles 中,就需要在运行时加载、实例化和释放它们。 Addressables 使用引用计数系统来确保 assets 只在需要时保留在内存中。 Addressables 初始化 Addressables 系统在运行时第一次加载 Addressable …

oracle事务隔离级别

文章目录 数据库事务概况1.事务相关概念2.事务的四个特性ACID : oracle事务隔离级别1 .两个事务并发访问数据库数据时可能存在的问题 数据库事务概况 一组SQL,一个逻辑工作单位,执行时整体修改或者整体回退。 1.事务相关概念 1)事务的提交和回滚&…

MongoDB-聚合查询操作介绍

目录 前言db.collection.aggregate()db.collection.aggregate()示例一db.collection.aggregate()示例二 复杂的聚合操作通过mongoimport导入测试数据复杂的聚合操作示例聚合操作的偏好设置-project函数聚合操作的偏好设置-unwind函数 前言 MongoDB聚合操作相当于关系型数据库S…

MATLAB基础应用精讲-【数模应用】Poisson检验(附MATLAB、python和R语言代码实现)

目录 几个高频面试题目 泊松分布的现实意义是什么,为什么现实生活多数服从于泊松分布? 算法原理 数学模型 参数估计与假设检验 (一) 参数估计 (二) 回归系数的假设检验 (三) 拟合优度检验 适用条件 SPSSPRO 1、作用 2、输入输出描述 3、案例示例 4、案例数据 …

区块链技术介绍

一.概述 1.什么是区块链? 区块链是一种分布式数据库技术,它以链式数据结构的形式存储数据,每个数据块与前一个数据块相关联,形成了一个不断增长的数据链。每个数据块中包含了一定数量的交易信息或其他数据,这些数据经…

Python可视化集大成之作 - Seaborn 介绍

我今天要介绍一款让你在数据可视化中游刃有余的利器——Seaborn包。作为Python数据可视化库中的一员,Seaborn不仅美观易用,而且功能丰富,是生物信息学中数据探索的好帮手。 为什么选择Seaborn? 1. 美观简洁 Seaborn的默认主题和颜…

遍历有向网格链路实现

在实际的业务中,我们可能遇到复杂规则(多个或与条件组合),复杂链路等类似场景问题,如:规则引擎相关业务,生产任务排期等。 复杂链路示意图如下: 复杂网路链路场景描述 有一个或多…

单片机学习笔记

一、单片机帝国的诞生与发展 1.1 单片机的基本概念 单片机是一种集成电路芯片,采用超大规模的集成电路把具有数据处理功能的中央处理器存储器、输入输出端口、外围电路和相关外设集成在一块硅片上构成一个小而完整的微型计算机系统。 一般而言,单片机也…

光电振荡器行业研究:未来几年年复合增长率CAGR为16.0%

光电振荡器(OEO)是一种微波光子系统,它使用高品质因数的光能量存储元件产生具有超低相位噪声的微波信号。光电振荡器基于将来自泵浦激光器的连续光能转换为射频(RF)、微波或毫米波信号。OEO 的特点是具有非常高的品质因数(Q) 和稳定性,以及电子振荡器不容…

K8s中如何使用etcd进行集群信息的备份与恢复

这里写目录标题 ETCD是什么?1. **`etcd`(服务)**2. **`etcdctl`(客户端工具)**如何安装etcdctl(客户端工具)查看目前K8s自带etcd中的版本信息安装对应版本的etcdutl工具下载 `etcdutl` 3.5.7 版本配置环境变量创建备份文件验证一下备份的快照文件备份文件恢复的效果演示…

从零开始学数据结构系列之第五章《B树的删除2》

文章目录 样例3情况3案例1案例2 总结往期回顾 样例3 原图,本例要删除50这个关键字,那这要怎么做呢? 思考ing 思考ing 思考ing 思考ing 思考ing 可以看到,最左边的子树是没有变化的,所以直接就不变,最…

语音测试(一)ffmpeg视频转音频

视频转音频 下载ffmpeg工具进入bin目录cmd进入控制台输入命令 ffmpeg.exe -i ./视频.mp4 ./音频.wav命令说明 ffmpeg -i input.mp4 output.mkv FFmpeg 可能会尝试自动选择合适的编码器对视频和音频进行重新编码,以便适应 MKV 格式的要求ffmpeg -i input.mp4 -c c…

Node.js 高级主题深度解析:性能优化、测试与日志管理

Node.js 高级主题深度解析:性能优化、测试与日志管理 目录 🚀 性能优化 🛠️ 使用 cluster 模块实现多进程🧠 内存泄漏分析与优化📊 性能分析工具的使用 🧪 测试 📑 单元测试和集成测试&#x…

使用Unity的准备

下载Unity 下载Unity Hub Unity - 实时内容开发平台 | 3D、2D、VR & AR可视化https://unity.cn/ 创建账号或者登入账号 Unity安装 路径尽量为英文路径 登入账号 点击头像登入账号 这里已经登入 打开偏好 设置中文 添加许可证 获取免费版的即可 安装编辑器 新建项目…

【HTML】置换元素(替换元素)

● 它的内容不是由元素的标签内的内容决定的&#xff0c;而是由元素的属性决定的 ● 可以通过CSS设置宽度和高度。 常见的置换元素主要包括以下几种&#xff1a; <img> 元素&#xff1a;用于嵌入图像&#xff0c;通过 src 属性指定图像的路径。例如&#xff1a;<img…

pdf在线转换成word免费版,一键免费转换

在日常的学习和办公中&#xff0c;PDF文件和Word文档是我们离不开的两种最常见的文件&#xff0c;而PDF与Word文档之间的转换成为了我们日常工作中不可或缺的一部分。无论是为了编辑、修改还是共享文件&#xff0c;掌握多种PDF转Word的方法都显得尤为重要。很多小伙伴关心能不能…

2023年公共英语三级考试阅读经典试题及译文答案

2023年公共英语三级考试阅读经典试题及译文答案 Flying over a desert area in an airplane, two scientists looked down with trained eyes at treesand bushes. After an hour s flight, one of the scientists wrote in his book, "Look here for probable metal. &qu…

智能对决:提示词攻防中的AI安全博弈

智能对决&#xff1a;提示词攻防中的AI安全博弈 在2024年上海AIGC开发者大会上&#xff0c;知名提示词爱好者工程师云中嘉树发表了关于AI提示词攻防与安全博弈的精彩演讲。他深入探讨了当前AI产品的安全现状&#xff0c;提示词攻击的常见手段及其应对策略。本文将对他的演讲进…

Charles抓包全流程(Mac端+iOS端)

文章目录 与其他抓包软件的对比FiddlerWireShark Charles下载安装及配置Charles抓包实践小结 Charles Proxy是一个广泛使用的网络调试代理工具&#xff0c;它允许开发者监控和分析所有经过计算机的HTTP和SSL/HTTPS网络流量信息。 与其他抓包软件的对比 Fiddler Charles 支持多…