VulkanMemory

我们最近在合Epic p4上的一些修复​36229276,44931331,34369995,32679349,32854759,34132292,然后提交给AI做Review后发现3个问题,让AI检查了了一下当前UE5Main官方代码是否有同样的问题,AI回复好像问题也是有的;

这三个问题分别是:

​ 1. [Critical] VulkanMemory.cpp — FTempBlockAllocator 不能真正 grow

确认存在问题。

VulkanMemory.cpp:4780 的断言写错了:

checkfSlow(BlockAlignment < BlockSize, TEXT(“… size of %d (%d aligned) is too large for block size of %d”), InSize, AlignedSize, BlockSize);

条件是 BlockAlignment < BlockSize,这是一个静态恒真条件(alignment=16 < blockSize=4MB 永真),应该是 AlignedSize <= BlockSize。这意味着:

- 超过 BlockSize 的请求永远不会被拦截

- fetch_add(AlignedSize) 会让 CurrentOffset 超出 block 边界

- AllocOffset + InSize < BlockSize 失败后进入加锁路径,分配一个新的同样 BlockSize 大小的 block — 还是放不下

- 最终命中 checkSlow(AllocOffset + InSize < BlockSize) (line 4823),但这只在 Debug slow-check 构建中触发

- Release/Shipping 构建中直接越界写入 → GPU fault 风险

另外 AllocBlock() (line 4746) 始终用固定 BlockSize 创建 buffer,没有任何 grow 逻辑。

2. [High] VulkanBuffer.cpp — Lock 的 else 分支不保留旧数据

确认存在问题。

VulkanBuffer.cpp:431-449,Lock 函数的 else 分支:

FBufferAlloc NewAlloc;

AllocateMemory(NewAlloc); // 全新 buffer,内容未初始化

NewAlloc.Alloc.Disown();

// … swap in on RHI thread, free old buffer

Data = NewAlloc.HostPtr;

DataOffset = Offset;

这个分支在以下条件触发:dynamic/unified buffer,非首次 lock,WriteOnly 模式(非 NoOverwrite),非 staging 强制,非 sparse。它会直接替换整个 backing store。

如果是 partial lock(Offset != 0 或 LockSize != GetSize()),新 buffer 中未被写入的区域是脏数据/未初始化内存。虽然 RLM_WriteOnly 契约通常隐含 discard,但如果上层代码依赖保留 buffer

的前/后部分(比如只更新后半段),就会出现随机渲染错误。

实际触发概率取决于上层使用模式,但代码层面确实缺乏防御——没有 assert 检查 partial lock,也没有 copy-on-write。

3. [High] VulkanBuffer.cpp — staging copy 前缺 barrier

确认存在问题。

VulkanBuffer.cpp:505,Unlock 的 staging copy 路径,copy 前只有:

VulkanRHI::DebugHeavyWeightBarrier(CommandBufferHandle, 16); // 非 Debug 构建为空操作

而 DebugHeavyWeightBarrier 的定义在 VulkanRHIPrivate.h:631-637:

inline void DebugHeavyWeightBarrier(VkCommandBuffer CmdBuffer, int32 CVarConditionMask)

{

#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT

if (CVarVulkanDebugBarrier.GetValueOnAnyThread() & CVarConditionMask)

{

 HeavyWeightBarrier(CmdBuffer);

}

#endif

}

在 Shipping/Test 构建中编译为空。copy 后的 BarrierAfter(line 515-516)保留了,但 copy 前没有真正的 read→write 依赖保护。如果之前有 GPU 操作在读这个 buffer,staging copy 可能与之并行执行,产生

WRITE_AFTER_READ hazard。

截图中提到之前加的 SYNC-HAZARD-WRITE-AFTER-READ fix 被这次合并直接移除了,Shipping 构建有 sync hazard 回归风险。

[Attachment Removed]

Hi,

感谢反馈,

第一个问题,应该是笔误,改成checkfSlow(AlignedSize < BlockSize, …)应该就可以了吧? 他所谓的没有增长,我觉得是有问题的,他只是按BlockSize来增长,可以看BusyBlocks和AvailableBlocks

第二个问题,应该不是什么问题,如果能走到这个else,一般都是需要整个buffer要lock的,因为前面已经考虑过bDynamic,或者bEntireBuffer的情况了

第三个问题,这里不是靠Barrier,而是通过FlushMappedMemory来做CPU->GPU的同步,所以没关系。

[Attachment Removed]

好的好的,感谢回复,我们再研究一下

[Attachment Removed]