资讯详情

资讯详情

AnyPS5 GPU驱动深度解析(二):PM4命令缓冲解码完全拆解

AnyPS5 GPU驱动深度解析(二)PM4命令缓冲解码完全拆解【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/GitHub_Trending/an/AnyPS5在 AnyPS5 这个把 PS5 可执行文件自动移植到 Linux / Windows 的工具中GPU 驱动是让游戏画面真正跑起来的核心环节。本文深入拆解 AnyPS5 的PM4 命令缓冲解码流程从 32 位包头如何决定整个指令包的结构到 54 个 GPU 操作码如何被校验、分发与执行带你完整看懂这份给 GPU 下的指令单是如何被逐条翻译成本地 Vulkan 调用的。1️⃣ 什么是 PM4写给 GPU 的点菜清单如果把 CPU 的机器码比作给 CPU 下的指令那么PM4 就是 AMD GPU 专用的指令格式。PS5 游戏运行时guest并不直接操作显卡而是把一条条 PM4 指令按顺序写进一个 32 位字dword序列里提交给 GPU 的命令处理器CP, Command Processor。在 AnyPS5 中guest 程序调用的libSceAgc等系统库负责做菜组织这些指令而 libSceAgcDriver 则负责端菜把提交上来的 PM4 命令流逐包读出、校验、解释再翻译成宿主机 GPU 能理解的操作。整个解码逻辑集中在两个文件声明与辅助判断Pm4.hpp校验/执行/命名实现Pm4.cpp2️⃣ 包头解剖一个 32 位整数如何描述整个包PM4 解码的第一步就是读懂每个包的头部 dword。它把这个包是什么类型、有多长、是什么操作全编码在一个整数里位域含义说明31:30包类型0b10 填充包(filler)0b11 带负载的数据包29:16负载长度数据包的有效 dword 数不含头部本身15:8操作码 (opcode)决定这条指令做什么如0x27 DRAW_INDEX_27:0标志位如 bit0 表示受谓词条件(predicated)控制等源码里两个小函数就能算出这个包总共有几个 dwordFillerPacket(header)类型字段为 2 的就是填充包只占 1 个 dword纯粹用来对齐PacketWords(header)数据包总长 负载数 2头部加一个尾标记。这个规则定义在 Pm4.hpp解码主循环正是靠它从一个 dword 流里切出一个包接一个包。3️⃣ 操作码全表驱动认识 54 条指令 11 条自定义包AnyPS5 在 Pm4Opcodes.hpp 中维护了一份操作码目录共 54 项按功能大致可以分成四类分类代表操作码作用 绘制 (Draw)DRAW_INDEX_2 (0x27)、DRAW_INDEX_AUTO (0x2D)、DRAW_INDEX_OFFSET_2 (0x35)提交顶点/实例绘制⚡ 间接绘制DRAW_INDIRECT (0x24)、DRAW_INDEX_INDIRECT_MULTI (0x38)绘制参数从显存读取数量可动态变化 计算 (Compute)DISPATCH_DIRECT (0x15)、DISPATCH_INDIRECT (0x16)派发 compute shader 工作组 内存与同步COPY_DATA (0x40)、DMA_DATA (0x50)、WRITE_DATA (0x37)、WAIT_REG_MEM (0x3C)、RELEASE_MEM (0x49)数据拷贝、DMA 传输、等待内存/写回标签⚙️ 寄存器SET_CONTEXT_REG (0x69)、SET_UCONFIG_REG (0x79)、SET_SH_REG (0x76)写 GPU 内部状态寄存器 条件控制COND_EXEC (0x22)、SET_PREDICATION (0x20)按内存值决定执行或跳过后续包 有趣的细节PS5 的 AGC 驱动还在标准 PM4 里藏了11 个自定义 NOP 包如FLIP、WAIT_FLIP_DONE、PUSH_MARKER、CONTEXT_STATE它们借用 opcode0x10NOP的低 6 位做子命令。解码器在 Pm4.cpp 的Name()里把它们逐一识别命名——例如FLIP就是交换前后缓冲区、完成一帧呈现的关键包。对于未实现的操作码驱动不会猜测行为而是给出明确的拒绝原因UnsupportedReason()比如0x3F INDIRECT_BUFFER会报嵌套命令缓冲未实现。这种宁缺毋滥的设计保证了画面错误的可追溯性相关已知限制记录在 docs/dev/TechnicalDebt.md。4️⃣ 三步解码流水线校验 → 分发 → 执行真正逐包处理命令流的是驱动工作线程。主循环位于 PacketExecution.cpp对每个提交Submission做如下处理切包从当前游标读 header用PacketWords()算出包边界跳过 filler谓词检查若包头带 predicated 标志且当前有生效的谓词条件先判断条件是否满足不满足就整包甚至整个COND_EXEC段跳过校验Pm4::Validate(packet, queue)检查包大小、对齐、地址位宽、队列归属图形包不许出现在计算队列等分类执行WAIT_REG_MEM / WAIT_REG_MEM_640x3C/0x93→ 轮询 guest 内存直到比较条件成立支持 7 种比较函数见 Pm4.cppFLIP / WAIT_FLIP_DONE→ 提交已录制的 Vulkan 命令并交换显示缓冲DISPATCH_* / DRAW_*→ 走图形/计算录制路径其余纯状态包寄存器写入、拷贝、DMA、写标签→ 交给Pm4::Execute()在 CPU 侧直接模拟。第 3、4 步中的轻量操作都由 Pm4.cpp 里的Validate()第 201 行起和Execute()第 709 行起承担两者对同一份包头做同一种解码保证校验与执行语义完全一致。5️⃣ 队列状态机解码的工作台PM4 指令很少是孤立生效的——DRAW_INDEX_2要画多少顶点取决于之前INDEX_TYPE、INDEX_BUFFER_SIZE、NUM_INSTANCES写过什么。因此解码器为每条 GPU 队列维护一份QueueState定义在 QueueState.hpp成员用途context / userConfig / shader三组寄存器文件分别承接SET_CONTEXT_REG、SET_UCONFIG_REG、SET_SH_REG的写入constantRam0x3000 个 dword每队列私有的常量 RAMWRITE_CONST_RAM / DUMP_CONST_RAM读写它indexBase / indexType / indexBufferSize索引缓冲状态供后续绘制解析drawIndirectBase / dispatchIndirectBaseSET_BASE (0x11)写入的间接参数基址predication当前谓词条件地址 操作 执行方向markersPUSH/POP_MARKER维护的调试标记栈一个小而精的设计寄存器文件用位图 稀疏数组实现QueueState.hpp只记录被写过的寄存器InitialContextRegisters()则预置了硬件默认的 context 寄存器初值保证 guest 首次读取时行为与真机一致。6️⃣ 关键包拆解绘制参数是怎么解出来的以最复杂的间接绘制为例。guest 只提交一条 5 个 dword 的DRAW_INDEX_INDIRECT真正的顶点数藏在显存的记录里。解码过程在ResolveDraw()Pm4.cpp中完成基址取自SET_BASE写入的drawIndirectBase索引绘制要检查INDEX_BUFFER_SIZE × 索引字节宽的范围合法性0x24A (GE_INDX_OFFSET)寄存器决定起始顶点偏移非索引绘制与索引绘制的记录分别是 16 / 20 字节字段布局见 Pm4.hpp 中IndirectDraw的注释。另一个高频包WAIT_REG_MEM的解码要点地址取packet[2] | packet[3] 3264 位版为 9 dword 的WAIT_REG_MEM_64比较函数由低 3 位编码等于、小于、不等于……共 7 种参考值按 mask 屏蔽后比较驱动通过GuestMemory的受检读路径轮询该地址确保读到的是 guest GPU 工作完成后的最新值见 Pm4.cpp。这套机制就是CPU 侧标签 (fence)的本质前一队列用RELEASE_MEM写入计数后一队列用WAIT_REG_MEM等它帧序同步就此成立。7️⃣ 动手验证调试开关与测试想亲眼看看解码过程项目内置了几个环境变量开关在 PacketExecution.cpp 中读取APS5_TRACE_GPU1打印每帧提交、每个包的 opcode 与负载APS5_DUMP_QUEUE队列十六进制把命令流按包名逐行 dump 出来[queue] DRAW_INDEX_2 ...非常适合对照本文第 2 节的包头位域手工验证APS5_PROFILE_DRAW1统计 dispatch / draw / wait 各阶段耗时。行为层面的回归则交给 tests/Pm4.cpp它构造合成包头逐一验证 54 个操作码的命名、54 项目录无重复、11 个自定义 NOP 命名以及寄存器写入、间接寄存器、越界/未对齐等必须被拒绝的负例——解码器不认识的一律拒绝正是它能稳定移植大量游戏的基础。8️⃣ 小结问题答案PM4 是什么AMD GPU 的命令缓冲指令格式PS5 图形/计算流水线的提交语言解码从哪开始32 位 header31:30定类型、29:16定长度、15:8定操作码驱动认识多少指令54 个标准操作码 11 个自定义 NOP 包未实现者明确拒绝解码流程切包 → 谓词检查 →Validate()→ 分类分发 →Execute()/ 录制状态存哪每队列一份QueueState寄存器文件、常量 RAM、索引状态、谓词至此AnyPS5 的 PM4 解码链路已经全部走通guest 提交的每一个 dword 都能被定位、命名、校验并复现硬件语义。在下一篇中我们将沿着FLIP包继续往下游走看看解码后的绘制是如何被录制进 Vulkan 命令缓冲并完成最终呈现的。【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/GitHub_Trending/an/AnyPS5创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →