JoshWindsor 发表于 2023-1-20 10:38

【UnrealEngine5】扩展EMeshPass位宽

一切的起源

晚上好!夜之城,昨天的死人大乐透...啊,不,晚上好!知乎,又是欢乐躺坑的一天!昨天QA一共报了30个bug!但是我看你们全得赔,因为昨天出了一个P0!
一切的起源是因为某天我在手摇引擎的过程中抄了一下上面这个大佬写的描边Pass,但是由于我人菜瘾大刚把ue搬到5.1了,又刚好EMeshPass只有32位,因此我的共享单车引擎光荣熄火了。在经过了Brotato真好玩、SteamDeck真好玩、大河内真他妈尊重、没有孤独摇滚看我要死了和一月新番开播之后我终于想起来我的引擎还是趴窝的状态,所以今天又捡起来重新整了一下,依然是血压高了但没完全高的状态,非常健全非常健康。那么惯例免责声明,我是垃圾,求轻喷。


绿皮时间

依然惯例的,先进行一下绿皮操作,让我们先按照教程来,加个pass。


好第一个比较正经的报错来了,这个也挺简单的,改掉就完事了,把28改成29就没问题了


然后改位宽,比较好玩的事情,ue其实已经感觉不够用了,所以加了下面两个static_assert,但是他就是没送佛送到西。



改位宽不然下面两个static_assert会报错

然后报错就开始诡异了起来,基本是撒得到处都是的状态,根据一顿绿皮之后发现是FMeshPassMask::Data塞不下了,因此直接暴力修改


然后就编译器警告(等级 3)C4334了,没事,类型有问题,那就强转,突出一个小作坊,突出一个暴力,于是这个类变成了下面这样
class FMeshPassMask
{
public:
        FMeshPassMask()
                : Data(0)
        {
        }

        void Set(EMeshPass::Type Pass)
        {
                Data |= (uint64(1) << Pass);
        }

        bool Get(EMeshPass::Type Pass) const
        {
                return !!(Data & (uint64(1) << Pass));
        }

        EMeshPass::Type SkipEmpty(EMeshPass::Type Pass) const
        {
                uint64 Mask = 0xFFffFFffFFffFFffULL << Pass;
                return EMeshPass::Type(FMath::Min<uint64>(EMeshPass::Num, FMath::CountTrailingZeros(Data & Mask)));
        }

        int GetNum()
        {
                return FMath::CountBits(Data);
        }

        void AppendTo(FMeshPassMask& Mask) const
        {
                Mask.Data |= Data;
        }

        void Reset()
        {
                Data = 0;
        }

        bool IsEmpty() const
        {
                return Data == 0;
        }

        uint64 Data;
};
之后还有PSO的报错,改,MaxPSOCollectorCount改成33就行


其实到这里按理讲已经能手摇起来了,至少也是以奇怪的方式正常运行了,其实扒拉UE自己的ue5-main分支,其实他们也是这么改的。But something went wrong.
血压时间

其实我在抄那篇教程的第一天就这么改了,但是实际是根本跑不动,因此我很好奇epic的ue5-main分支是不是根本跑不起来(其实能跑,因为另一个commit里面修了这个问题,我没看到)。


报错,爆出来是这样的:


非常的新鲜,死在了这里,数组下标越界了,非常神必,而且这坨代码已经好几年没人更新了。没有办法放弃绿皮,开始展现自己配得上自己的工资。
首先,来看看这坨主要目的是啥
EMeshPass::Type PassType = EMeshPass::DepthPass;
int NewPrefixSum = PrefixSum;
for (;;)
{
        PassType = MeshRelevance.CommandInfosMask.SkipEmpty(PassType);
        if (PassType == EMeshPass::Num)
        {
                break;
        }

        int CommandInfoIndex = MeshIndex * EMeshPass::Num + PassType;
        checkSlow(CommandInfoIndex >= NewPrefixSum);
        SceneInfo->StaticMeshCommandInfos = SceneInfo->StaticMeshCommandInfos;
        NewPrefixSum++;
        PassType = EMeshPass::Type(PassType + 1);
}
根据我的绿皮经验,大概是重新排序一下,把不执行的pass直接挑掉。逻辑上没有问题,理论上不可能会出问题。现在回想起来我当时把上下全部扒了一遍就是纯纯的弱智。反过来思考,数组下标越界只有可能是NewPrefixSum 这个值出去了,那么他出去的原因只有if(PassType == EMeshPass::Num)这个条件永远不会被满足,来断点一下:


刚开始是正常的,PassType能够正常对上,但是,从第32开始,事情变得诡异了起来


不管Sum如何增加,PassType都会卡在EditorSelection,其实这里已经可以定位了,SkipEmpty这个函数因为不知道什么原因无法正常返回EMeshPass::Num了,但是当时我脑子可能抽搐的很厉害,并没往这里想,害得我绕了一大圈。


让我们回到这个SkipEmpty里面,并且把他拆开来





可以看到33进来变成了32

然后我们就能得到答案了,FMath::CountTrailingZeros 这个函数最大只能返回32,但是,我们加了一个pass,EMeshPass::Num这个值是33,原本拿来防止你乱写值进去强制限制输出的地方是这次问题的元凶。


解决

解决方案那就很简单了,直接改一下就完事了,UE已经提供了64位版本的FMath::CountTrailingZeros64 ,改成下面这样就行了:
EMeshPass::Type SkipEmpty(EMeshPass::Type Pass) const
{
        uint64 Mask = 0xFFffFFffFFffFFffULL << Pass;
        return EMeshPass::Type(FMath::Min<uint64>(EMeshPass::Num, FMath::CountTrailingZeros64(Data & Mask)));
}
walaa,能跑了,描边也出来了。WOoooOoo~(但是为啥好像描边是透明的,估计哪里绿皮的时候shader抄错了)


结语

又是一个说大很小,说小我又让引擎趴窝了好久的问题,原本打算等epic更新5.2,现在想想当时的我智商可能受到了打击。(不过Epic自己也没发现这个修改并不能解决pass的问题,而且等了三个月也没改,四舍五入我比epic的工程师牛逼)(今天起来看了一下epic其实早修了,呜呜呜,我是傻逼)


那么我们下个坑再见


更新

原本还想提个pr刷个成就,然后在GitHub上面一通扒拉,发现其实有人已经修了,而且修了好久了,我是傻逼。怪不得ue5-main没人管,原来好早前就修了,只不过commit没写meshpass没搜到(


参考


[*]^编译器警告(等级 3)C4334https://learn.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/ke55d167(v=vs.100)
页: [1]
查看完整版本: 【UnrealEngine5】扩展EMeshPass位宽