refactor(gpu): use a single ordered PM4 scheduler

This commit is contained in:
nmzik
2026-07-22 23:15:03 +02:00
parent dbcb444c05
commit f60007e923
12 changed files with 680 additions and 918 deletions
-96
View File
@@ -1,96 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
#include "common/common.h"
#include "common/profiler.h"
#include "common/threads.h"
#include <functional>
#include <string>
#include <utility>
namespace Libs::Graphics {
class AsyncJob final {
public:
using Task = std::function<void()>;
explicit AsyncJob(std::string thread_name = {})
: m_thread_name(std::move(thread_name)), m_worker_thread(WorkerEntry, this) {}
~AsyncJob() {
{
Common::LockGuard lock(m_mutex);
m_stop_requested = true;
m_task_available_condition.Signal();
}
m_worker_thread.Join();
}
KYTY_CLASS_NO_COPY(AsyncJob);
void Execute(Task task) {
Common::LockGuard lock(m_mutex);
while (m_is_busy) {
m_idle_condition.Wait(&m_mutex);
}
m_task = std::move(task);
m_is_busy = true;
m_task_available_condition.Signal();
}
void Wait() {
Common::LockGuard lock(m_mutex);
while (m_is_busy) {
m_idle_condition.Wait(&m_mutex);
}
}
private:
static void WorkerEntry(void* data) { static_cast<AsyncJob*>(data)->WorkerLoop(); }
void WorkerLoop() {
if (!m_thread_name.empty()) {
KYTY_PROFILER_THREAD(m_thread_name.c_str());
}
for (;;) {
Task task;
{
Common::LockGuard lock(m_mutex);
while (!m_is_busy && !m_stop_requested) {
m_task_available_condition.Wait(&m_mutex);
}
if (!m_is_busy) {
return;
}
task = std::move(m_task);
}
task();
{
Common::LockGuard lock(m_mutex);
m_is_busy = false;
m_idle_condition.SignalAll();
}
}
}
std::string m_thread_name;
Common::Mutex m_mutex;
Common::CondVar m_task_available_condition;
Common::CondVar m_idle_condition;
Task m_task;
bool m_is_busy = false;
bool m_stop_requested = false;
Common::Thread m_worker_thread;
};
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_ */
@@ -2,12 +2,10 @@
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
#include "common/assert.h"
#include "common/threads.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include <array>
#include <cstdint>
#include <vector>
@@ -17,94 +15,25 @@ inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
class CommandScheduler {
enum class Pm4ProcessResult { Complete, Blocked };
class Pm4Execution {
public:
static constexpr int BuffersNum = 8;
CommandScheduler(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
bool Active() const { return m_current >= 0 && m_current < BuffersNum; }
void CheckActive() const { EXIT_IF(!Active()); }
RenderCommandBuffer& Current() const {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return *m_buffers[m_current];
}
void Init() {
if (m_current >= 0) {
return;
}
for (auto& buf: m_buffers) {
EXIT_IF(buf != nullptr);
buf = new RenderCommandBuffer(m_registers, m_user_config, m_shaders);
}
m_current = 0;
Current().Begin();
}
void Flush() {
SubmitCurrent();
BeginNext();
}
CommandBuffer& FlushAndGetSubmitted() {
auto& submitted = SubmitCurrent();
BeginNext();
return submitted;
}
void CopyBuffers(std::array<CommandBuffer*, BuffersNum>& out) const {
for (int i = 0; i < BuffersNum; i++) {
auto* buf = m_buffers[i];
EXIT_IF(buf == nullptr);
out[i] = buf;
}
}
void WaitAll() {
for (auto* buf: m_buffers) {
EXIT_IF(buf == nullptr);
buf->WaitForFenceAndReset();
}
}
void SubmitForReadback() {
if (!Active()) {
return;
}
SubmitCurrent();
}
void ResumeAfterReadback() {
if (!Active()) {
return;
}
Current().WaitForFenceAndReset();
Current().Begin();
}
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
private:
CommandBuffer& SubmitCurrent() {
auto& submitted = Current();
submitted.End();
submitted.Execute();
return submitted;
}
friend class CommandProcessor;
void BeginNext() {
m_current = (m_current + 1) % BuffersNum;
Current().WaitForFenceAndReset();
Current().Begin();
}
struct BufferCursor {
uint32_t* next_packet = nullptr;
uint32_t remaining_dw = 0;
uint32_t total_dw = 0;
uint32_t deferred_advance_dw = 0;
};
RenderCommandBuffer* m_buffers[BuffersNum] = {};
int m_current = -1;
HW::Context& m_registers;
HW::UserConfig& m_user_config;
HW::Shader& m_shaders;
std::vector<BufferCursor> m_buffer_stack;
bool m_suspended = false;
bool m_made_progress = false;
};
class CommandProcessor {
@@ -116,8 +45,8 @@ public:
int64_t flip_arg = 0;
};
CommandProcessor();
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; }
CommandProcessor() = default;
~CommandProcessor() = default;
KYTY_CLASS_NO_COPY(CommandProcessor);
@@ -128,7 +57,6 @@ public:
void BufferFlushAndWait();
void BufferWait();
void BeginReadbackTransaction() {
m_mutex.Lock();
if (m_readback_active) {
EXIT("nested command-processor readback transaction\n");
}
@@ -142,12 +70,8 @@ public:
}
m_readback_active = false;
m_readback_finished = false;
m_mutex.Unlock();
}
void RunLock() { m_run_mutex.Lock(); }
void RunUnlock() { m_run_mutex.Unlock(); }
HW::Context& GetCtx() { return m_ctx; }
HW::UserConfig& GetUcfg() { return m_ucfg; }
HW::Shader& GetShCtx() { return m_sh_ctx; }
@@ -202,10 +126,11 @@ public:
void PrefetchL2(void* addr, uint32_t size) {}
void ResetDeCe();
void SetCeComplete(bool complete) { m_ce_complete = complete; }
void WaitCe();
void WaitDeDiff(uint32_t diff);
void IncremenetDe();
void IncremenetCe();
void IncrementDe();
void IncrementCe();
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
@@ -222,30 +147,26 @@ public:
const volatile void* address, uint32_t count_in_dwords);
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
void Run(uint32_t* data, uint32_t num_dw);
Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
private:
struct Counter {
Common::Mutex mutex;
Common::CondVar cond_var;
uint32_t value = 0;
};
template <typename T>
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
uint32_t interrupt_context_id);
void FinishCommandProcessors();
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void SuspendPm4();
RenderCommandBuffer& CurrentBuffer() { return m_scheduler.Current(); }
void CheckBuffer() const { m_scheduler.CheckActive(); }
CommandScheduler& GetScheduler() const { return GetRenderContext().GetCommandScheduler(); }
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return GetRenderContext().GetGpuResources(); }
HW::Context m_ctx;
@@ -259,16 +180,11 @@ private:
uint64_t m_dispatch_indirect_args_base_addr = 0;
uint32_t m_num_instances = 1;
inline static Common::Mutex m_mutex;
inline static std::vector<CommandProcessor*> m_processors;
inline static bool m_readback_active = false;
inline static bool m_readback_finished = false;
Common::Mutex m_run_mutex;
CommandScheduler m_scheduler;
Counter m_de_counter;
Counter m_ce_counter;
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
bool m_readback_active = false;
bool m_readback_finished = false;
uint32_t m_const_ram[0x3000] = {0};
@@ -1984,7 +1984,6 @@ KYTY_CP_OP_PARSER(CpOpAcquireMem) {
}
if (stall_mode == 0) {
cp.BufferFlush();
cp.BufferWait();
}
@@ -2166,10 +2165,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
if (take_then) {
cp.Run(then_buffer, then_num_dw);
cp.ProcessIndirectBuffer(then_buffer, then_num_dw);
} else if (mode == 2 && else_num_dw != 0) {
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
cp.Run(else_buffer, else_num_dw);
cp.ProcessIndirectBuffer(else_buffer, else_num_dw);
}
return payload_dw;
@@ -2496,7 +2495,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementCeCounter) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008400);
EXIT_NOT_IMPLEMENTED(buffer[0] != 1);
cp.IncremenetCe();
cp.IncrementCe();
return 1;
}
@@ -2507,7 +2506,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementDeCounter) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008500);
EXIT_NOT_IMPLEMENTED(buffer[0] != 0);
cp.IncremenetDe();
cp.IncrementDe();
return 1;
}
@@ -2607,7 +2606,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
cp.Run(indirect_buffer, indirect_num_dw);
cp.ProcessIndirectBuffer(indirect_buffer, indirect_num_dw);
return 3;
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -27,7 +27,7 @@ void GraphicsRunDone();
int GraphicsRunGetFrameNum();
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
[[nodiscard]] CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept;
void GraphicsRunFinishCommandProcessors();
void GraphicsRunFinishScheduler();
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
} // namespace Libs::Graphics
@@ -554,7 +554,7 @@ bool BufferCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint6
return action == CpuFaultAction::Continue;
}
if (GraphicsRunIsCommandProcessorThread()) {
GraphicsRunFinishCommandProcessors();
GraphicsRunFinishScheduler();
}
m_readback->Request(access, vaddr, size);
return true;
@@ -0,0 +1,92 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
namespace Libs::Graphics {
void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config,
HW::Shader& shaders) {
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>();
}
m_current = 0;
}
BindCurrent();
if (!m_recording) {
Current().Begin();
m_recording = true;
}
}
void CommandScheduler::Flush() {
SubmitCurrent();
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
auto& submitted = SubmitCurrent();
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
if (m_recording) {
SubmitCurrent();
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
auto& submitted = SubmitCurrent();
submitted.WaitForFenceAndReset();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount);
}
RenderCommandBuffer& CommandScheduler::Current() const {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return *m_buffers[m_current];
}
void CommandScheduler::BindCurrent() const {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
Current().Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent() {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
submitted.Execute();
m_recording = false;
return submitted;
}
void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
Current().WaitForFenceAndReset();
BindCurrent();
Current().Begin();
m_recording = true;
}
} // namespace Libs::Graphics
@@ -0,0 +1,45 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#include "common/common.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <memory>
namespace Libs::Graphics {
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler() = default;
~CommandScheduler() = default;
KYTY_CLASS_NO_COPY(CommandScheduler);
void Begin(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders);
void Flush();
CommandBuffer& FlushAndGetSubmitted();
void Finish();
void FinishCurrent();
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
private:
void BindCurrent() const;
CommandBuffer& SubmitCurrent();
void BeginNext();
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
+13 -8
View File
@@ -129,17 +129,22 @@ private:
class RenderCommandBuffer final: public CommandBuffer {
public:
RenderCommandBuffer(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
RenderCommandBuffer() = default;
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return m_shaders; }
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
}
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
private:
HW::Context& m_registers;
HW::UserConfig& m_user_config;
HW::Shader& m_shaders;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
@@ -6,6 +6,7 @@
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/gdsBuffer.h"
@@ -30,6 +31,7 @@ public:
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
Common::Mutex& GetMutex() { return m_mutex; }
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
FramebufferCache& GetFramebufferCache() { return m_framebuffer_cache; }
@@ -52,6 +54,7 @@ private:
GraphicContext& m_graphics;
Common::Mutex m_mutex;
CommandScheduler m_command_scheduler;
PipelineCache m_pipeline_cache;
DescriptorCache m_descriptor_cache;
FramebufferCache m_framebuffer_cache;
@@ -3653,7 +3653,7 @@ bool TextureCache::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint
if (needs_readback) {
if (GraphicsRunIsCommandProcessorThread()) {
GraphicsRunFinishCommandProcessors();
GraphicsRunFinishScheduler();
}
m_readback->Request(access, vaddr, size);
action = m_memory_tracker.BeginCpuFault(vaddr, size, access);
+101
View File
@@ -3,9 +3,12 @@
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/gpuTiler.h"
#include "graphics/host_gpu/hostMemory.h"
@@ -14083,6 +14086,102 @@ void CheckClipControlDepthClipState() {
std::printf("[host] %-32s ok\n", "ClipControlDepthClipState");
}
void CheckPm4WaitResume() {
GraphicsInitJmpTables();
CommandProcessor processor;
uint32_t label = 0;
uint32_t prefix = 0;
uint32_t child_observation = UINT32_MAX;
uint32_t suffix = 0;
const auto address = [](const void *value) {
return reinterpret_cast<uint64_t>(value);
};
std::array<uint32_t, 12> child{};
child[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
child[1] = 0;
child[2] = static_cast<uint32_t>(address(&child_observation));
child[3] = static_cast<uint32_t>(address(&child_observation) >> 32u);
child[4] = 0;
child[5] = KYTY_PM4(7, Pm4::IT_NOP, Pm4::R_WAIT_MEM_32);
child[6] = static_cast<uint32_t>(address(&label));
child[7] = static_cast<uint32_t>(address(&label) >> 32u);
child[8] = UINT32_MAX;
child[9] = 1;
child[10] = 0x10u | 3u;
std::array<uint32_t, 4> nested{};
nested[0] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
nested[1] = static_cast<uint32_t>(address(child.data()));
nested[2] = static_cast<uint32_t>(address(child.data()) >> 32u);
nested[3] = 0x0f200000u | static_cast<uint32_t>(child.size());
std::array<uint32_t, 14> commands{};
commands[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[1] = 0;
commands[2] = static_cast<uint32_t>(address(&prefix));
commands[3] = static_cast<uint32_t>(address(&prefix) >> 32u);
commands[4] = 11;
commands[5] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
commands[6] = static_cast<uint32_t>(address(nested.data()));
commands[7] = static_cast<uint32_t>(address(nested.data()) >> 32u);
commands[8] = 0x0f200000u | static_cast<uint32_t>(nested.size());
commands[9] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[10] = 0;
commands[11] = static_cast<uint32_t>(address(&suffix));
commands[12] = static_cast<uint32_t>(address(&suffix) >> 32u);
commands[13] = 22;
Pm4Execution execution;
Require("Pm4WaitResume", "suspend",
processor.Process(execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Blocked &&
prefix == 11 && child_observation == 0 && suffix == 0,
"blocked indirect wait did not preserve its command position");
label = 1;
child[4] = 1;
Require("Pm4WaitResume", "resume",
processor.Process(execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Complete &&
prefix == 11 && child_observation == 0 && suffix == 22,
"resumed indirect wait replayed a child or did not finish its parent");
std::printf("[host] %-32s ok\n", "Pm4WaitResume");
}
void CheckPm4CeCompletion() {
GraphicsInitJmpTables();
CommandProcessor processor;
uint32_t suffix = 0;
const auto address = reinterpret_cast<uint64_t>(&suffix);
std::array<uint32_t, 7> commands{};
commands[0] = 0xc0008600u;
commands[1] = 1;
commands[2] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[3] = 0;
commands[4] = static_cast<uint32_t>(address);
commands[5] = static_cast<uint32_t>(address >> 32u);
commands[6] = 33;
processor.ResetDeCe();
Pm4Execution execution;
Require("Pm4CeCompletion", "wait",
processor.Process(execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Blocked &&
suffix == 0,
"DE did not wait for an active CE stream");
processor.SetCeComplete(true);
Require("Pm4CeCompletion", "complete",
processor.Process(execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Complete &&
suffix == 33,
"DE remained blocked after the CE stream completed");
std::printf("[host] %-32s ok\n", "Pm4CeCompletion");
}
} // namespace
} // namespace Libs::Graphics
@@ -14229,6 +14328,8 @@ int main(int argc, char **argv) {
#endif
CheckClipControlDepthClipState();
CheckReferenceClockScale();
CheckPm4WaitResume();
CheckPm4CeCompletion();
CheckEmbeddedFetchVertexOffset();
CheckEmbeddedFetchLaneSpill();
CheckPs5GameExampleImageClearRuntimeShape();