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 跟踪和预热

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/875492.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Blender渲染太慢怎么办?blender云渲染已开启

动画行业蓬勃发展,动画制作软件亦持续推陈出新,当制作平台日益丰富,创作难度降低,创作效率提升,如何高效完成复杂动画的渲染就成了从业者更关心的问题。 云渲染技术的出现,无疑为动画制作者提供了前所未有…

kafka原理剖析及实战演练

一、消息系统概述 一)消息系统按消息发送模型分类 1、peer-to-peer(单播) 特点: 一般基于pull或polling接收消息发送对队列中的消息被一个而且仅仅一个接收者所接收,即使有多个接收者在同一队列中侦听同一消息即支持异…

利用熵权法进行数值评分计算——算法过程

1、概述 在软件系统中,研发人员常常遇上需要对系统内的某种行为/模型进行评分的情况。例如根据系统的各种漏洞情况对系统安全性进行评分、根据业务员最近操作系统的情况对业务员工作状态进行打分等等。显然研发人员了解一种或者几种标准评分算法是非常有利于开展研…

中控室控制台处在自动状态什么意思

在现代工业和智能控制系统中,中控室控制台作为集中控制和管理各种设备、系统和流程的核心,扮演着至关重要的角色。当提到中控室控制台处在自动状态时,这通常意味着控制台已经切换到一种高度智能化的工作模式,能够自动调整和管理各…

【SQL】百题计划:SQL判断条件OR的使用。

【SQL】百题计划-20240912 Select name, population, area from World where area>3000000 or population > 25000000;

品读 Java 经典巨著《Effective Java》90条编程法则,第4条:通过私有构造器强化不可实例化的能力

文章目录 【前言】欢迎订阅【品读《Effective Java》】系列专栏java.lang.Math 类的设计经验总结 【前言】欢迎订阅【品读《Effective Java》】系列专栏 《Effective Java》是 Java 开发领域的经典著作,作者 Joshua Bloch 以丰富的经验和深入的知识,全面…

网络运输层之(1)TCP协议基础

网络运输层之(1)TCP协议基础 Author: Once Day Date: 2024年9月12日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-…

cv2.bitwise_or 提取ROI区域

原图如下所示,想提取圆形ROI区域,红色框 img np.ones(ori_img.shape, dtype"uint8") img img * 255 cv2.circle(img, (50,50), 50, 0, -1) self.bitwiseOr cv2.bitwise_or(ori_img, circle)使用一个和原图尺寸一致的图像做mask,图白圆黑 以…

通信工程学习:什么是PC永久连接、SPC软永久连接

一、PC永久连接 PC(Permanent Connection)永久连接是一种由网管系统通过网管协议建立的长期稳定的连接方式。在ASON(自动交换光网络)中,PC永久连接沿袭了传统光网络的连接建立形式,其特点主要包括&#xff…

视频监控平台是如何运作的?EasyCVR视频汇聚平台的高效策略与实践

随着科技的飞速发展,视频监控平台在社会安全、企业管理、智慧城市构建等领域发挥着越来越重要的作用。一个高效的视频监控平台,不仅依赖于先进的硬件设备,更离不开强大的视频处理技术作为支撑。这些平台集成了多种先进的视频技术,…

微波无源器件 OMT 2 倍频程带宽紧凑十字转门OMT

摘要: 一个64%瞬态带宽的可变比例十字转门OMT用于在所谓的延伸C频带卫星链接被提出。所体术的结构通过在四个输出矩形波导结处添加一个拓宽的单阶梯来克服现在的实际带宽限制。这个明智的(judicious)调整,和减高度波导和E面弯头的和功率合成器的使用,保证…

kali Linux 安装教程(保姆级)

kali 背景 基于Debian的Linux操作系统 Kali Linux是基于Debian的Linux发行版, 设计用于数字取证操作系统。每一季度更新一次。由Offensive Security Ltd维护和资助。最先由Offensive Security的Mati Aharoni和Devon Kearns通过重写BackTrack来完成,BackT…

专题三_二分查找算法_算法详细总结

目录 二分查找 1.⼆分查找(easy) 1)朴素二分查找,就是设mid(leftright)/2,xnums[mid],t就是我们要找的值 2)二分查找就是要求保证数组有序的前提下才能进行。 3)细节问题: 总结&#xff1a…

系统优化工具 | PC Cleaner v9.7.0.3 绿色版

PC Cleaner是一款功能强大的电脑清理和优化工具,旨在通过清理系统垃圾文件、解除恶意软件和优化系统性能来提高计算机的运行效率。该软件提供了多种功能,可以帮助用户维护和提升计算机的整体表现。 PC Cleaner 支持 Windows 7 及以上操作系统&#xff0…

Visual Studio Installer 2022 安装提示正在提取文件 进度条不动 0B每秒

Visual Studio Installer 稍等片刻...正在提取文件 进度条不动 0B每秒 一段时间后提示 循环下载安装文件 无法下载安装文件。请检查Internet 连接,然后重试 打开vs2017 或者vs2019或者vs2022的安装程序(visual studio installer)时,下载进度条不动&…

智能体 vs AI智能体:区别与联系,一文读懂!

​ 在AI技术蓬勃发展的今天,“智能体”(Agent)和”AI智能体”(AI Agent)两个概念经常被提及,二者在很多场合下会被混淆,但其实它们有着不同的定义和应用。我觉得很有必要小小科普下两者的定义与…

龙芯+FreeRTOS+LVGL实战笔记(新)——06添加二级按钮

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…

mysql学习教程,从入门到精通,SQL AND OR 运算符(12)

1、SQL AND & OR 运算符 在本教程中,您将学习如何在子句中使用ASELECT column1_name, column2_name, columnN_nameFROM table_nameWHERE condition1 AND condition2;ND&OR运算符,WHERE以根据多个条件过滤记录。 1.1、根据条件选择记录 …

LCSS—最长回文子序列

思路分析 关于”回文串“的问题,是面试中常见的,本文提升难度,讲一讲”最长回文子序列“问题,题目很好理解: 输入一个字符串 s,请找出 s 中的最长回文子序列长度。 比如输入 s"aecda"&#xff0c…

考题:将数组的元素内容反转

考题:把数组的元素内容反转,int[ ] arr {11,22,33,44,55};变成int[ ] arr{55,44,33,22,11} 小伙伴们可以自己先思考再看解析: 方法1: 思想:两杯水交换的思想(有一杯装满水的杯子a和一杯装满水的杯子b,想想…