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

题图(取自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.rhkb.cn/news/418194.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习中的聚类艺术:探索数据的隐秘之美

一 什么是聚类 聚类是一种经典的无监督学习方法,无监督学习的目标是通过对无标记训练样本的学习,发掘和揭示数据集本身潜在的结构与规律,即不依赖于训练数据集的类标记信息。聚类则是试图将数据集的样本划分为若干个互不相交的类簇&#xff…

关于武汉高芯coin417G2红外机芯的二次开发

文章目录 前言一、外观和机芯参数二、SDK的使用1、打开相机2、回调函数中获取全局温度和图像3、关闭相机 前言 最近工作中接触了一款基于武汉高芯科技有限公司开发的红外模组,即coin417g2(测温型)9.1mm镜头.使用此模组,开发了一套红外热成像检测桌面应用程序.下面简单记录下该…

PHP轻量级高性能HTTP服务框架 - webman

摘要 webman 是一款基于 workerman 开发的高性能 HTTP 服务框架。webman 用于替代传统的 php-fpm 架构,提供超高性能可扩展的 HTTP 服务。你可以用 webman 开发网站,也可以开发 HTTP 接口或者微服务。 除此之外,webman 还支持自定义进程&am…

UE5 C++ 读取图片插件(一)

原来UE可以使用 static,之前不知道&#xff0c;一用就报错。 static TSharedPtr<IImageWrapper> GetImageWrapperByExtention(const FString InImagePath); //智能指针&#xff0c;方便追寻引用C,加载ImageWrapperstatic UTexture2D* LoadTexture2D(const FString& …

大路灯护眼灯有必要吗安全吗?性价比高落地护眼灯推荐

大路灯护眼灯有必要吗安全吗&#xff1f;近几年来&#xff0c;随着生活节奏的加快&#xff0c;目前青少年的近视率呈现一个直线上升的趋势&#xff0c;其中占比达到了70%以上&#xff0c;并且最令人意外的是小学生竟然也占着比较大的比重&#xff0c;这一系列的数据不仅表明着近…

Kafka【五】Buffer Cache (缓冲区缓存)、Page Cache (页缓存)和零拷贝技术

【1】Buffer Cache (缓冲区缓存) 在Linux操作系统中&#xff0c;Buffer Cache&#xff08;缓冲区缓存&#xff09;是内核用来优化对块设备&#xff08;如磁盘&#xff09;读写操作的一种机制&#xff08;故而有一种说法叫做块缓存&#xff09;。尽管在较新的Linux内核版本中&a…

联众优车持续加大汽车金融服务投入与创新,赋能汽车消费新生态

近年来&#xff0c;中国汽车消费市场呈现出蓬勃发展的态势&#xff0c;而汽车金融服务作为降低购车门槛、优化购车体验的重要手段&#xff0c;正日益受到市场的青睐。《2023中国汽车消费趋势调查报告》显示&#xff0c;相较于前一年&#xff0c;今年选择汽车金融服务的市场消费…

实战docker第二天——cuda11.8,pytorch基础环境docker打包

在容器化环境中打包CUDA和PyTorch基础环境&#xff0c;可以将所有相关的软件依赖和配置封装在一个Docker镜像中。这种方法确保了在不同环境中运行应用程序时的一致性和可移植性&#xff1a; Docker&#xff1a;提供了容器化技术&#xff0c;通过将应用程序及其所有依赖打包在一…

天然药物化学史话:甾体化合物-文献精读45(甾体化合物化学历史综述-地表最强综述系列-45)

天然药物化学史话:甾体化合物&#xff0c;极好的一篇综述&#xff0c;地表最强综述系列-45 摘要 甾体化合物是一类重要的天然产物,在人类发展史上不仅为人类的健康做出了特殊的贡献,而且其立体结构的特殊性方面也在有机化学发展史,特别是有机化学理论上占有极其重要的地位,完善…

如何为 DigitalOcean 静态路由操作员设置故障转移

静态路由操作器的主要目的是提供更大的灵活性&#xff0c;并在 Kubernetes 环境中控制网络流量。它使你能够根据应用程序的需求自定义路由配置&#xff0c;从而优化网络性能。该操作器作为 DaemonSet 部署&#xff0c;因此将在你的 DigitalOcean Managed Kubernetes 集群的每个…

6.2高斯滤波

目录 实验原理 示例代码1 运行结果1 示例代码2 运行结果2 实验代码3 运行结果3 实验原理 在OpenCV中&#xff0c;高斯滤波&#xff08;Gaussian Filtering&#xff09;是一种非常常用的图像平滑处理方法。它通过使用一个高斯核&#xff08;即高斯分布函数&#xff09;对…

南通网站建设手机版网页

随着移动互联网的迅猛发展&#xff0c;越来越多的人通过手机浏览网页&#xff0c;进行在线购物、信息查询和社交互动。因此&#xff0c;建立一个适合移动端访问的网站已成为企业和个人不可忽视的重要任务。在南通&#xff0c;网站建设手机版网页的需求逐渐增加&#xff0c;如何…

单体到微服务:架构变迁

单体架构与微服务架构&#xff1a;从单体到微服务的演变 引言单体架构概述微服务架构的优势一、功能定位二、使用场景三、配置方式四、性能特点Eureka - 服务注册与发现框架核心功能工作原理优势应用场景 结论 引言 在软件开发的世界中&#xff0c;随着业务的增长和技术的发展…

艾体宝洞察丨透过语义缓存,实现更快、更智能的LLM应用程序

传统的缓存只存储数据而不考虑上下文&#xff0c;语义缓存则不同&#xff0c;它能理解用户查询背后的含义。它使数据访问更快&#xff0c;系统响应更智能&#xff0c;对 GenAI 应用程序至关重要。 什么是语义缓存&#xff1f; 语义缓存解释并存储用户查询的语义&#xff0c;使…

MQTT broker搭建并用SSL加密

系统为centos&#xff0c;基于emqx搭建broker&#xff0c;流程参考官方。 安装好后&#xff0c;用ssl加密。 进入/etc/emqx/certs,可以看到 分别为 cacert.pem CA 文件cert.pem 服务端证书key.pem 服务端keyclient-cert.pem 客户端证书client-key.pem 客户端key 编辑emqx配…

云计算之大数据(下)

目录 一、Hologres 1.1 产品定义 1.2 产品架构 1.3 Hologres基本概念 1.4 最佳实践 - Hologres分区表 1.5 最佳实践 - 分区字段设置 1.6 最佳实践 - 设置字段类型 1.7 最佳实践 - 存储属性设置 1.8 最佳实践 - 分布键设置 1.9 最佳实践 - 聚簇键设置 1.10 最佳实践 -…

12. GIS地图制图工程师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

多线程 | ThreadLocal源码分析

文章目录 1. ThreadLocal解决了什么问题数据隔离避免参数传递资源管理 2. ThreadLocal和Synchronized3. ThreadLocal核心核心特性常见方法使用场景注意事项 4. ThreadLocal如何实现线程隔离的&#xff1f;&#xff08;重点&#xff09;ThreadLocal 的自动清理与内存泄漏问题阿里…

浙大数据结构:02-线性结构3 Reversing Linked List

数据结构MOOC PTA习题 这道题也是相当费事&#xff0c;不过比上一个题好一些&#xff0c;这里我使用了C的STL库&#xff0c;使得代码量大幅减少。 题干机翻&#xff1a; 1、条件准备 这里我准备采用map来存地址和值&#xff0c;因为map的查找效率也是不错的 数组arr是存链…

GPU环境配置:1.CUDA、Anaconda、Pytorch

一、查看显卡适配CUDA型号 查看自己电脑的显卡版本&#xff1a; 在 Windows 设置中查看显卡型号&#xff1a;使用 Windows I 快捷键打开「设置」&#xff0c;依次点击「系统」-「屏幕」和「高级显示器设置」&#xff0c;在「显示器 1」旁边就可以看到显卡名称。 右键点菜单图标…