mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
Fix unnecessary queue drains in predication and suspend points
This commit is contained in:
@@ -62,7 +62,6 @@ public:
|
|||||||
|
|
||||||
void BufferInit();
|
void BufferInit();
|
||||||
void BufferFlush();
|
void BufferFlush();
|
||||||
void BufferFlushAndWait();
|
|
||||||
void BufferWait();
|
void BufferWait();
|
||||||
HW::Context& GetCtx() { return m_ctx; }
|
HW::Context& GetCtx() { return m_ctx; }
|
||||||
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ namespace Libs::Graphics {
|
|||||||
|
|
||||||
static thread_local CommandProcessor* g_current_processor = nullptr;
|
static thread_local CommandProcessor* g_current_processor = nullptr;
|
||||||
static thread_local Pm4Execution* g_current_execution = nullptr;
|
static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||||
static thread_local bool g_gpu_mutex_owned = false;
|
|
||||||
static thread_local bool g_gpu_thread = false;
|
static thread_local bool g_gpu_thread = false;
|
||||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||||
|
|
||||||
@@ -54,27 +53,6 @@ struct DrawIndexedIndirectArgs {
|
|||||||
uint32_t start_instance_location;
|
uint32_t start_instance_location;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GpuMutexLock final {
|
|
||||||
public:
|
|
||||||
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
|
|
||||||
if (g_gpu_mutex_owned) {
|
|
||||||
EXIT("recursive GPU mutex acquisition\n");
|
|
||||||
}
|
|
||||||
g_gpu_mutex_owned = true;
|
|
||||||
m_mutex.Lock();
|
|
||||||
}
|
|
||||||
~GpuMutexLock() {
|
|
||||||
if (!g_gpu_mutex_owned) {
|
|
||||||
EXIT("invalid GPU mutex release\n");
|
|
||||||
}
|
|
||||||
m_mutex.Unlock();
|
|
||||||
g_gpu_mutex_owned = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Common::Mutex& m_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool GraphicsRunDebugDumpEnabled() {
|
static bool GraphicsRunDebugDumpEnabled() {
|
||||||
return Config::GraphicsDebugDumpEnabled() &&
|
return Config::GraphicsDebugDumpEnabled() &&
|
||||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||||
@@ -84,6 +62,7 @@ GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
|
|||||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||||
GraphicsInitJmpTables();
|
GraphicsInitJmpTables();
|
||||||
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
|
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
|
||||||
|
m_gfx_cp->Reset();
|
||||||
m_thread = std::jthread(ThreadRun, this);
|
m_thread = std::jthread(ThreadRun, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,20 +139,16 @@ void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
|||||||
if (draw_commands.empty()) {
|
if (draw_commands.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GpuMutexLock lock(m_submission_mutex);
|
Submission submission;
|
||||||
Submission submission;
|
|
||||||
submission.type = SubmissionType::Graphics;
|
submission.type = SubmissionType::Graphics;
|
||||||
submission.queue_id = 0;
|
submission.queue_id = 0;
|
||||||
submission.commands = draw_commands;
|
submission.commands = draw_commands;
|
||||||
submission.constant_commands = constant_commands;
|
submission.constant_commands = constant_commands;
|
||||||
submission.reset_processor = m_graphics_done;
|
|
||||||
m_graphics_done = false;
|
|
||||||
Enqueue(std::move(submission));
|
Enqueue(std::move(submission));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
|
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
|
||||||
EXIT_IF(commands.empty());
|
EXIT_IF(commands.empty());
|
||||||
GpuMutexLock lock(m_submission_mutex);
|
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
|
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
|
||||||
|
|
||||||
@@ -186,22 +161,21 @@ void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
|
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
|
||||||
GpuMutexLock lock(m_submission_mutex);
|
Submission submission;
|
||||||
Submission submission;
|
|
||||||
submission.type = SubmissionType::FlipPreparation;
|
submission.type = SubmissionType::FlipPreparation;
|
||||||
submission.queue_id = 0;
|
submission.queue_id = 0;
|
||||||
submission.reset_processor = m_graphics_done;
|
|
||||||
submission.flip_request_id = request_id;
|
submission.flip_request_id = request_id;
|
||||||
m_graphics_done = false;
|
|
||||||
Enqueue(std::move(submission));
|
Enqueue(std::move(submission));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::Done() {
|
void GuestGpu::SuspendPoint() {
|
||||||
GpuMutexLock lock(m_submission_mutex);
|
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||||
if (!IsGpuThread()) {
|
// Do not hold a queue lock while waiting: asynchronous work may be needed to
|
||||||
WaitForIdle();
|
// finish the preceding graphics frame. The first point returns immediately.
|
||||||
}
|
m_suspend_point_ready->acquire();
|
||||||
m_graphics_done = true;
|
Submission submission;
|
||||||
|
submission.type = SubmissionType::SuspendPoint;
|
||||||
|
Enqueue(std::move(submission));
|
||||||
m_done_num++;
|
m_done_num++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +203,9 @@ void CommandProcessor::Reset() {
|
|||||||
m_context_state_pushed = false;
|
m_context_state_pushed = false;
|
||||||
m_index_type_and_size = 0;
|
m_index_type_and_size = 0;
|
||||||
m_index_buffer_size = 0;
|
m_index_buffer_size = 0;
|
||||||
|
m_index_base_addr = 0;
|
||||||
|
m_num_instances = 1;
|
||||||
|
m_predicate_skip = false;
|
||||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||||
m_draw_indirect_args_base_addr = 0;
|
m_draw_indirect_args_base_addr = 0;
|
||||||
m_dispatch_indirect_args_base_addr = 0;
|
m_dispatch_indirect_args_base_addr = 0;
|
||||||
@@ -268,10 +245,6 @@ void CommandProcessor::BufferFlush() {
|
|||||||
GetScheduler().Flush();
|
GetScheduler().Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::BufferFlushAndWait() {
|
|
||||||
GetScheduler().FlushAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandProcessor::BufferWait() {
|
void CommandProcessor::BufferWait() {
|
||||||
BufferInit();
|
BufferInit();
|
||||||
GetScheduler().Finish();
|
GetScheduler().Finish();
|
||||||
@@ -459,6 +432,7 @@ void GuestGpu::Enqueue(Submission submission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::WaitForIdle() {
|
void GuestGpu::WaitForIdle() {
|
||||||
|
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||||
Common::LockGuard lock(m_queue_mutex);
|
Common::LockGuard lock(m_queue_mutex);
|
||||||
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
|
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
|
||||||
m_idle.Wait(&m_queue_mutex);
|
m_idle.Wait(&m_queue_mutex);
|
||||||
@@ -564,11 +538,7 @@ void GuestGpu::ThreadRun(void* data) {
|
|||||||
|
|
||||||
bool GuestGpu::Process(Submission& submission) {
|
bool GuestGpu::Process(Submission& submission) {
|
||||||
const bool first_slice = !submission.started;
|
const bool first_slice = !submission.started;
|
||||||
auto& cp = GetProcessor(submission.queue_id);
|
auto& cp = GetProcessor(submission.queue_id);
|
||||||
|
|
||||||
if (first_slice && submission.reset_processor) {
|
|
||||||
cp.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first_slice) {
|
if (first_slice) {
|
||||||
submission.started = true;
|
submission.started = true;
|
||||||
@@ -645,6 +615,13 @@ bool GuestGpu::Process(Submission& submission) {
|
|||||||
m_renderer.RunGarbageCollector();
|
m_renderer.RunGarbageCollector();
|
||||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||||
break;
|
break;
|
||||||
|
case SubmissionType::SuspendPoint:
|
||||||
|
cp.EmitGlobalBarrier();
|
||||||
|
m_renderer.GetCommandScheduler().DeferPriorityOperation(
|
||||||
|
[ready = m_suspend_point_ready] { ready->release(); });
|
||||||
|
cp.BufferFlush();
|
||||||
|
cp.Reset();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return complete;
|
return complete;
|
||||||
@@ -849,9 +826,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 0x03:
|
case 0x03:
|
||||||
if (wait_op != 0) {
|
// The wait selector applies only to Z-pass query readiness.
|
||||||
BufferFlushAndWait();
|
|
||||||
}
|
|
||||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||||
value = *reinterpret_cast<const volatile uint64_t*>(address);
|
value = *reinterpret_cast<const volatile uint64_t*>(address);
|
||||||
break;
|
break;
|
||||||
@@ -1034,9 +1009,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
|||||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||||
|
|
||||||
uint32_t frame_num = 0;
|
uint32_t frame_num = 0;
|
||||||
// uint32_t local_x = 1;
|
|
||||||
// uint32_t local_y = 1;
|
|
||||||
// uint32_t local_z = 1;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
frame_num = m_renderer.GetGpu().GetFrameNum();
|
frame_num = m_renderer.GetGpu().GetFrameNum();
|
||||||
@@ -1058,35 +1030,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& cs = m_sh_ctx.GetCs().cs_regs;
|
|
||||||
// local_x = std::max(cs.num_thread_x, 1u);
|
|
||||||
// local_y = std::max(cs.num_thread_y, 1u);
|
|
||||||
// local_z = std::max(cs.num_thread_z, 1u);
|
|
||||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||||
thread_group_y, thread_group_z, mode);
|
thread_group_y, thread_group_z, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
|
|
||||||
auto group_count = [](uint32_t threads, uint32_t group_size) {
|
|
||||||
return (threads == 0
|
|
||||||
? 0u
|
|
||||||
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
|
|
||||||
};
|
|
||||||
|
|
||||||
auto groups_x = thread_group_x;
|
|
||||||
auto groups_y = thread_group_y;
|
|
||||||
auto groups_z = thread_group_z;
|
|
||||||
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
|
|
||||||
groups_x = group_count(thread_group_x, local_x);
|
|
||||||
groups_y = group_count(thread_group_y, local_y);
|
|
||||||
groups_z = group_count(thread_group_z, local_z);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint64_t invocations =
|
|
||||||
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
|
|
||||||
if (invocations != 0) {
|
|
||||||
BufferFlushAndWait();
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <semaphore>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -32,11 +33,14 @@ public:
|
|||||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||||
|
|
||||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||||
void Submit(std::span<const uint32_t> draw_commands,
|
void Submit(std::span<const uint32_t> draw_commands,
|
||||||
std::span<const uint32_t> constant_commands);
|
std::span<const uint32_t> constant_commands);
|
||||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||||
void SubmitFlipPreparation(uint64_t request_id);
|
void SubmitFlipPreparation(uint64_t request_id);
|
||||||
void Done();
|
// Insert an ordered graphics drain; only a previous suspend point can block the caller.
|
||||||
|
void SuspendPoint();
|
||||||
|
// Wait for guest command processing, including all compute queues (not native GPU completion).
|
||||||
|
void WaitForIdle();
|
||||||
[[nodiscard]] int GetFrameNum() const;
|
[[nodiscard]] int GetFrameNum() const;
|
||||||
|
|
||||||
[[nodiscard]] static bool IsGpuThread() noexcept;
|
[[nodiscard]] static bool IsGpuThread() noexcept;
|
||||||
@@ -48,7 +52,7 @@ private:
|
|||||||
static constexpr uint32_t ComputeQueueBase = 0x20;
|
static constexpr uint32_t ComputeQueueBase = 0x20;
|
||||||
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
|
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
|
||||||
|
|
||||||
enum class SubmissionType { Graphics, Compute, FlipPreparation };
|
enum class SubmissionType { Graphics, Compute, FlipPreparation, SuspendPoint };
|
||||||
|
|
||||||
struct Submission {
|
struct Submission {
|
||||||
SubmissionType type = SubmissionType::Graphics;
|
SubmissionType type = SubmissionType::Graphics;
|
||||||
@@ -57,7 +61,6 @@ private:
|
|||||||
std::span<const uint32_t> constant_commands;
|
std::span<const uint32_t> constant_commands;
|
||||||
Pm4Execution command_execution;
|
Pm4Execution command_execution;
|
||||||
Pm4Execution constant_execution;
|
Pm4Execution constant_execution;
|
||||||
bool reset_processor = false;
|
|
||||||
bool started = false;
|
bool started = false;
|
||||||
bool command_complete = false;
|
bool command_complete = false;
|
||||||
bool constant_complete = false;
|
bool constant_complete = false;
|
||||||
@@ -66,14 +69,12 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Enqueue(Submission submission);
|
void Enqueue(Submission submission);
|
||||||
void WaitForIdle();
|
|
||||||
void ProcessCommands();
|
void ProcessCommands();
|
||||||
bool Process(Submission& submission);
|
bool Process(Submission& submission);
|
||||||
static void ThreadRun(void* data);
|
static void ThreadRun(void* data);
|
||||||
CommandProcessor& GetProcessor(uint32_t queue_id);
|
CommandProcessor& GetProcessor(uint32_t queue_id);
|
||||||
|
|
||||||
RenderContext& m_renderer;
|
RenderContext& m_renderer;
|
||||||
Common::Mutex m_submission_mutex;
|
|
||||||
Common::Mutex m_queue_mutex;
|
Common::Mutex m_queue_mutex;
|
||||||
std::mutex m_shutdown_mutex;
|
std::mutex m_shutdown_mutex;
|
||||||
Common::CondVar m_work_available;
|
Common::CondVar m_work_available;
|
||||||
@@ -84,10 +85,12 @@ private:
|
|||||||
uint32_t m_next_queue = 0;
|
uint32_t m_next_queue = 0;
|
||||||
uint32_t m_submission_count = 0;
|
uint32_t m_submission_count = 0;
|
||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_graphics_done = true;
|
|
||||||
bool m_accepting = true;
|
bool m_accepting = true;
|
||||||
bool m_stopping = false;
|
bool m_stopping = false;
|
||||||
bool m_shutdown_complete = false;
|
bool m_shutdown_complete = false;
|
||||||
|
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||||
|
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||||
|
std::make_shared<std::binary_semaphore>(1);
|
||||||
|
|
||||||
std::unique_ptr<CommandProcessor> m_gfx_cp;
|
std::unique_ptr<CommandProcessor> m_gfx_cp;
|
||||||
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
|
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
|
||||||
|
|||||||
+1
-1
@@ -1552,7 +1552,7 @@ int KYTY_SYSV_ABI AgcSuspendPoint() {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
EXIT_IF(g_renderer == nullptr);
|
EXIT_IF(g_renderer == nullptr);
|
||||||
g_renderer->GetGpu().Done();
|
g_renderer->GetGpu().SuspendPoint();
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2402,6 +2402,232 @@ public:
|
|||||||
std::printf("[host] %-32s ok\n", "StreamBufferRing");
|
std::printf("[host] %-32s ok\n", "StreamBufferRing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckGpuSuspendPoint() {
|
||||||
|
constexpr const char *name = "GpuSuspendPoint";
|
||||||
|
EnsureRuntimeContext();
|
||||||
|
auto &context = Renderer();
|
||||||
|
context.InitializeGpu(nullptr);
|
||||||
|
auto &gpu = context.GetGpu();
|
||||||
|
auto &scheduler = context.GetCommandScheduler();
|
||||||
|
CommandProcessor setup(context, 0);
|
||||||
|
|
||||||
|
vk::SemaphoreTypeCreateInfo timeline_type{};
|
||||||
|
timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
||||||
|
timeline_type.semaphoreType = vk::SemaphoreType::eTimeline;
|
||||||
|
vk::SemaphoreCreateInfo timeline_create{};
|
||||||
|
timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo;
|
||||||
|
timeline_create.pNext = &timeline_type;
|
||||||
|
vk::Semaphore gate = nullptr;
|
||||||
|
Require(name, "timeline create",
|
||||||
|
m_runtime_context.device.createSemaphore(
|
||||||
|
&timeline_create, nullptr, &gate) == vk::Result::eSuccess,
|
||||||
|
"failed to create the native completion gate");
|
||||||
|
const auto signal = [&](uint64_t value) {
|
||||||
|
vk::SemaphoreSignalInfo info{};
|
||||||
|
info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
||||||
|
info.semaphore = gate;
|
||||||
|
info.value = value;
|
||||||
|
Require(name, "timeline signal",
|
||||||
|
m_runtime_context.device.signalSemaphore(&info) ==
|
||||||
|
vk::Result::eSuccess,
|
||||||
|
"failed to release native GPU work");
|
||||||
|
};
|
||||||
|
const auto finish = [&] {
|
||||||
|
gpu.WaitForIdle();
|
||||||
|
gpu.SendCommandSync([&] {
|
||||||
|
scheduler.Finish();
|
||||||
|
scheduler.WaitPriorityOperations(scheduler.CurrentTick() - 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const auto write = [](uint32_t *packet, uint32_t *destination,
|
||||||
|
uint32_t value, bool predicated = false) {
|
||||||
|
const auto address = reinterpret_cast<uint64_t>(destination);
|
||||||
|
packet[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0) | uint32_t{predicated};
|
||||||
|
packet[1] = 0;
|
||||||
|
packet[2] = static_cast<uint32_t>(address);
|
||||||
|
packet[3] = static_cast<uint32_t>(address >> 32u);
|
||||||
|
packet[4] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
gpu.SendCommandSync([&] {
|
||||||
|
setup.BufferInit();
|
||||||
|
SubmitInfo blocked;
|
||||||
|
blocked.AddWait(gate, 1);
|
||||||
|
scheduler.Flush(blocked);
|
||||||
|
});
|
||||||
|
std::binary_semaphore first_returned{0};
|
||||||
|
std::jthread first([&] {
|
||||||
|
gpu.SuspendPoint();
|
||||||
|
first_returned.release();
|
||||||
|
});
|
||||||
|
const bool first_nonblocking =
|
||||||
|
first_returned.try_acquire_for(std::chrono::seconds(2));
|
||||||
|
if (!first_nonblocking) {
|
||||||
|
signal(1);
|
||||||
|
}
|
||||||
|
first.join();
|
||||||
|
Require(name, "first call", first_nonblocking,
|
||||||
|
"the first suspend point waited for native GPU completion");
|
||||||
|
|
||||||
|
// Let the marker reach Vulkan while its completion remains gated.
|
||||||
|
std::binary_semaphore marker_parsed{0};
|
||||||
|
std::jthread parser([&] {
|
||||||
|
gpu.WaitForIdle();
|
||||||
|
marker_parsed.release();
|
||||||
|
});
|
||||||
|
const bool parser_nonblocking =
|
||||||
|
marker_parsed.try_acquire_for(std::chrono::seconds(2));
|
||||||
|
if (!parser_nonblocking) {
|
||||||
|
signal(1);
|
||||||
|
}
|
||||||
|
parser.join();
|
||||||
|
Require(name, "marker insertion", parser_nonblocking,
|
||||||
|
"inserting a suspend point blocked the GPU command thread");
|
||||||
|
|
||||||
|
std::binary_semaphore second_entered{0};
|
||||||
|
std::binary_semaphore second_returned{0};
|
||||||
|
std::jthread second([&] {
|
||||||
|
second_entered.release();
|
||||||
|
gpu.SuspendPoint();
|
||||||
|
second_returned.release();
|
||||||
|
});
|
||||||
|
second_entered.acquire();
|
||||||
|
const bool second_overtook_completion =
|
||||||
|
second_returned.try_acquire_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
uint32_t compute_value = 0;
|
||||||
|
std::array<uint32_t, 5> compute_commands{};
|
||||||
|
write(compute_commands.data(), &compute_value, 33);
|
||||||
|
bool host_lane_responded = false;
|
||||||
|
std::binary_semaphore compute_progress{0};
|
||||||
|
std::jthread compute([&] {
|
||||||
|
gpu.SubmitCompute(0x20, compute_commands);
|
||||||
|
gpu.SendCommandSync([&] { host_lane_responded = true; });
|
||||||
|
gpu.WaitForIdle();
|
||||||
|
compute_progress.release();
|
||||||
|
});
|
||||||
|
const bool compute_did_not_wait =
|
||||||
|
compute_progress.try_acquire_for(std::chrono::seconds(2));
|
||||||
|
signal(1);
|
||||||
|
second.join();
|
||||||
|
compute.join();
|
||||||
|
finish();
|
||||||
|
Require(name, "one outstanding point", !second_overtook_completion,
|
||||||
|
"a second suspend point returned before the first native completion");
|
||||||
|
Require(name, "independent queue progress",
|
||||||
|
compute_did_not_wait && host_lane_responded && compute_value == 33,
|
||||||
|
"waiting for a previous suspend point blocked compute submissions "
|
||||||
|
"or the host command lane");
|
||||||
|
Require(name, "frame count", gpu.GetFrameNum() == 2,
|
||||||
|
"suspend-point insertion did not advance the frame counter once");
|
||||||
|
|
||||||
|
// A marker must stay behind a stalled graphics stream, then clear its
|
||||||
|
// predicate before the next stream. Async compute releases that stream.
|
||||||
|
alignas(16) uint64_t predicate = 0;
|
||||||
|
uint32_t label = 0;
|
||||||
|
uint32_t prefix = 0;
|
||||||
|
uint32_t skipped = 0;
|
||||||
|
uint32_t after_reset = 0;
|
||||||
|
const auto predicate_address = reinterpret_cast<uint64_t>(&predicate);
|
||||||
|
const auto label_address = reinterpret_cast<uint64_t>(&label);
|
||||||
|
std::array<uint32_t, 21> before{};
|
||||||
|
before[0] = KYTY_PM4(4, Pm4::IT_SET_PREDICATION, 0);
|
||||||
|
before[1] = (3u << 16u) | (1u << 8u);
|
||||||
|
before[2] = static_cast<uint32_t>(predicate_address);
|
||||||
|
before[3] = static_cast<uint32_t>(predicate_address >> 32u);
|
||||||
|
write(before.data() + 4, &skipped, 11, true);
|
||||||
|
write(before.data() + 9, &prefix, 22);
|
||||||
|
before[14] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
||||||
|
before[15] = 0x10u | 3u;
|
||||||
|
before[16] = static_cast<uint32_t>(label_address);
|
||||||
|
before[17] = static_cast<uint32_t>(label_address >> 32u);
|
||||||
|
before[18] = 1;
|
||||||
|
before[19] = UINT32_MAX;
|
||||||
|
std::array<uint32_t, 5> after{};
|
||||||
|
write(after.data(), &after_reset, 44, true);
|
||||||
|
std::binary_semaphore lane_entered{0};
|
||||||
|
std::binary_semaphore release_lane{0};
|
||||||
|
gpu.SendCommand([&] {
|
||||||
|
lane_entered.release();
|
||||||
|
release_lane.acquire();
|
||||||
|
});
|
||||||
|
lane_entered.acquire();
|
||||||
|
gpu.Submit(before, {});
|
||||||
|
std::binary_semaphore queued_returned{0};
|
||||||
|
std::jthread queued([&] {
|
||||||
|
gpu.SuspendPoint();
|
||||||
|
queued_returned.release();
|
||||||
|
});
|
||||||
|
const bool queued_nonblocking =
|
||||||
|
queued_returned.try_acquire_for(std::chrono::seconds(2));
|
||||||
|
if (!queued_nonblocking) {
|
||||||
|
release_lane.release();
|
||||||
|
gpu.SendCommandSync([&] { label = 1; });
|
||||||
|
queued.join();
|
||||||
|
finish();
|
||||||
|
Require(name, "queued insertion", false,
|
||||||
|
"suspend-point insertion drained pending guest graphics");
|
||||||
|
}
|
||||||
|
queued.join();
|
||||||
|
gpu.Submit(after, {});
|
||||||
|
release_lane.release();
|
||||||
|
bool prefix_reached = false;
|
||||||
|
bool graphics_ordered = false;
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::seconds(2);
|
||||||
|
do {
|
||||||
|
gpu.SendCommandSync([&] {
|
||||||
|
prefix_reached = prefix == 22;
|
||||||
|
graphics_ordered = skipped == 0 && after_reset == 0;
|
||||||
|
});
|
||||||
|
std::this_thread::yield();
|
||||||
|
} while (!prefix_reached && std::chrono::steady_clock::now() < deadline);
|
||||||
|
std::array<uint32_t, 5> release_graphics{};
|
||||||
|
write(release_graphics.data(), &label, 1);
|
||||||
|
gpu.SubmitCompute(0x20, release_graphics);
|
||||||
|
finish();
|
||||||
|
Require(name, "graphics FIFO",
|
||||||
|
prefix_reached && graphics_ordered && skipped == 0,
|
||||||
|
"suspend point or later graphics overtook a stalled graphics stream");
|
||||||
|
Require(name, "graphics reset", after_reset == 44,
|
||||||
|
"suspend point preserved the previous frame's predicate");
|
||||||
|
|
||||||
|
// Delay the completion callback beyond the owner's shutdown when possible.
|
||||||
|
// Its credit must remain alive independently of the GuestGpu object.
|
||||||
|
std::binary_semaphore priority_entered{0};
|
||||||
|
std::binary_semaphore release_priority{0};
|
||||||
|
gpu.SendCommandSync([&] {
|
||||||
|
scheduler.DeferPriorityOperation([&] {
|
||||||
|
priority_entered.release();
|
||||||
|
release_priority.acquire();
|
||||||
|
});
|
||||||
|
SubmitInfo blocked;
|
||||||
|
blocked.AddWait(gate, 2);
|
||||||
|
scheduler.Flush(blocked);
|
||||||
|
});
|
||||||
|
gpu.SuspendPoint();
|
||||||
|
gpu.WaitForIdle();
|
||||||
|
std::binary_semaphore shutdown_returned{0};
|
||||||
|
std::jthread shutdown([&] {
|
||||||
|
context.ShutdownGpu();
|
||||||
|
shutdown_returned.release();
|
||||||
|
});
|
||||||
|
const bool shutdown_overtook_native =
|
||||||
|
shutdown_returned.try_acquire_for(std::chrono::milliseconds(100));
|
||||||
|
signal(2);
|
||||||
|
priority_entered.acquire();
|
||||||
|
if (!shutdown_overtook_native) {
|
||||||
|
(void)shutdown_returned.try_acquire_for(std::chrono::seconds(2));
|
||||||
|
}
|
||||||
|
release_priority.release();
|
||||||
|
shutdown.join();
|
||||||
|
scheduler.DrainPriorityOperations();
|
||||||
|
m_runtime_context.device.destroySemaphore(gate, nullptr);
|
||||||
|
Require(name, "shutdown native completion", !shutdown_overtook_native,
|
||||||
|
"shutdown returned while the suspend point was still executing");
|
||||||
|
std::printf("[host] %-32s ok\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckGpuCommandLane() {
|
void CheckGpuCommandLane() {
|
||||||
EnsureRuntimeContext();
|
EnsureRuntimeContext();
|
||||||
auto &context = Renderer();
|
auto &context = Renderer();
|
||||||
@@ -2647,7 +2873,7 @@ public:
|
|||||||
graphics_gate_entered.acquire();
|
graphics_gate_entered.acquire();
|
||||||
live_graphics_commands[4] = 22;
|
live_graphics_commands[4] = 22;
|
||||||
graphics_gate_release.release();
|
graphics_gate_release.release();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
Require("GpuCommandLane", "borrowed graphics commands",
|
Require("GpuCommandLane", "borrowed graphics commands",
|
||||||
live_graphics_value == 22,
|
live_graphics_value == 22,
|
||||||
"graphics submission executed a copied PM4 stream");
|
"graphics submission executed a copied PM4 stream");
|
||||||
@@ -2665,7 +2891,7 @@ public:
|
|||||||
compute_gate_entered.acquire();
|
compute_gate_entered.acquire();
|
||||||
live_compute_commands[4] = 44;
|
live_compute_commands[4] = 44;
|
||||||
compute_gate_release.release();
|
compute_gate_release.release();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
Require("GpuCommandLane", "borrowed compute commands",
|
Require("GpuCommandLane", "borrowed compute commands",
|
||||||
live_compute_value == 44,
|
live_compute_value == 44,
|
||||||
"compute submission executed a copied PM4 stream");
|
"compute submission executed a copied PM4 stream");
|
||||||
@@ -2721,7 +2947,7 @@ public:
|
|||||||
write_packet(no_interrupt_graphics_commands.data(),
|
write_packet(no_interrupt_graphics_commands.data(),
|
||||||
&no_interrupt_graphics_value, 55);
|
&no_interrupt_graphics_value, 55);
|
||||||
gpu.Submit(no_interrupt_graphics_commands, {});
|
gpu.Submit(no_interrupt_graphics_commands, {});
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
LibKernel::EventQueue::KernelEvent interrupt_event{};
|
LibKernel::EventQueue::KernelEvent interrupt_event{};
|
||||||
@@ -2745,7 +2971,7 @@ public:
|
|||||||
interrupt_gate_entered.acquire();
|
interrupt_gate_entered.acquire();
|
||||||
live_interrupt_commands[2] = 0;
|
live_interrupt_commands[2] = 0;
|
||||||
interrupt_gate_release.release();
|
interrupt_gate_release.release();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
interrupt_count = 0;
|
interrupt_count = 0;
|
||||||
@@ -2761,7 +2987,7 @@ public:
|
|||||||
auto graphics_interrupt_commands = make_interrupt_packet(
|
auto graphics_interrupt_commands = make_interrupt_packet(
|
||||||
1, 1, &graphics_interrupt_label, 0x11223344u, 0);
|
1, 1, &graphics_interrupt_label, 0x11223344u, 0);
|
||||||
gpu.Submit(graphics_interrupt_commands, {});
|
gpu.Submit(graphics_interrupt_commands, {});
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
interrupt_count = 0;
|
interrupt_count = 0;
|
||||||
@@ -2787,7 +3013,7 @@ public:
|
|||||||
compute_interrupt_gate_entered.acquire();
|
compute_interrupt_gate_entered.acquire();
|
||||||
compute_interrupt_commands[2] |= 1u << 24u;
|
compute_interrupt_commands[2] |= 1u << 24u;
|
||||||
compute_interrupt_gate_release.release();
|
compute_interrupt_gate_release.release();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
interrupt_count = 0;
|
interrupt_count = 0;
|
||||||
@@ -2803,7 +3029,7 @@ public:
|
|||||||
auto compute_clock_commands = make_interrupt_packet(
|
auto compute_clock_commands = make_interrupt_packet(
|
||||||
3, 1, &compute_clock_label, 0, 0x567u);
|
3, 1, &compute_clock_label, 0, 0x567u);
|
||||||
gpu.SubmitCompute(0x20, compute_clock_commands);
|
gpu.SubmitCompute(0x20, compute_clock_commands);
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
interrupt_count = 0;
|
interrupt_count = 0;
|
||||||
@@ -2821,7 +3047,7 @@ public:
|
|||||||
2, 2, &compute_done_label, compute_done_value, 0x678u);
|
2, 2, &compute_done_label, compute_done_value, 0x678u);
|
||||||
compute_done_commands[1] = 0x62fu; // CS_DONE, shader-done index, no GCR action.
|
compute_done_commands[1] = 0x62fu; // CS_DONE, shader-done index, no GCR action.
|
||||||
gpu.SubmitCompute(0x20, compute_done_commands);
|
gpu.SubmitCompute(0x20, compute_done_commands);
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
finish_gpu();
|
finish_gpu();
|
||||||
|
|
||||||
interrupt_count = 0;
|
interrupt_count = 0;
|
||||||
@@ -2896,7 +3122,7 @@ public:
|
|||||||
});
|
});
|
||||||
std::this_thread::yield();
|
std::this_thread::yield();
|
||||||
}
|
}
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
Require("GpuCommandLane", "packet-boundary command polling",
|
Require("GpuCommandLane", "packet-boundary command polling",
|
||||||
packet_marker_a_at_callback == 11 &&
|
packet_marker_a_at_callback == 11 &&
|
||||||
packet_marker_b_at_callback == 0 && packet_marker_a == 11 &&
|
packet_marker_b_at_callback == 0 && packet_marker_a == 11 &&
|
||||||
@@ -2934,21 +3160,21 @@ public:
|
|||||||
uint32_t ordered_suffix = 0;
|
uint32_t ordered_suffix = 0;
|
||||||
std::jthread ordered([&] {
|
std::jthread ordered([&] {
|
||||||
ordered_started.release();
|
ordered_started.release();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
ordered_suffix = suffix;
|
ordered_suffix = suffix;
|
||||||
ordered_finished = true;
|
ordered_finished = true;
|
||||||
});
|
});
|
||||||
ordered_started.acquire();
|
ordered_started.acquire();
|
||||||
gpu.SendCommandSync([&] {
|
gpu.SendCommandSync([&] {
|
||||||
Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(),
|
Require("GpuCommandLane", "guest queue idle fence", !ordered_finished.load(),
|
||||||
"submit done returned before a queued submission");
|
"idle fence returned before a queued submission");
|
||||||
label = 1;
|
label = 1;
|
||||||
});
|
});
|
||||||
ordered.join();
|
ordered.join();
|
||||||
Require("GpuCommandLane", "ordered completion",
|
Require("GpuCommandLane", "ordered completion",
|
||||||
prefix == 11 && suffix == 22 && ordered_suffix == 22 &&
|
prefix == 11 && suffix == 22 && ordered_suffix == 22 &&
|
||||||
ordered_finished.load(),
|
ordered_finished.load(),
|
||||||
"submit done did not drain prior PM4 work");
|
"idle fence did not drain prior PM4 work");
|
||||||
|
|
||||||
auto &resources = context;
|
auto &resources = context;
|
||||||
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
|
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
|
||||||
@@ -2971,7 +3197,7 @@ public:
|
|||||||
unmap_complete.acquire();
|
unmap_complete.acquire();
|
||||||
}
|
}
|
||||||
unmap_thread.join();
|
unmap_thread.join();
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
Require("GpuCommandLane", "unmap queue progress",
|
Require("GpuCommandLane", "unmap queue progress",
|
||||||
unmap_returned &&
|
unmap_returned &&
|
||||||
!resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
|
!resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
|
||||||
@@ -3098,7 +3324,7 @@ public:
|
|||||||
dma_cursor == dma_commands.size(),
|
dma_cursor == dma_commands.size(),
|
||||||
"DMA_DATA packet stream has the wrong size");
|
"DMA_DATA packet stream has the wrong size");
|
||||||
gpu.Submit(dma_commands, {});
|
gpu.Submit(dma_commands, {});
|
||||||
gpu.Done();
|
gpu.WaitForIdle();
|
||||||
constexpr uint32_t clean_fill_value = 0xdecafbad;
|
constexpr uint32_t clean_fill_value = 0xdecafbad;
|
||||||
gpu.SendCommandSync([&] {
|
gpu.SendCommandSync([&] {
|
||||||
auto &buffer_cache = resources.GetBufferCache();
|
auto &buffer_cache = resources.GetBufferCache();
|
||||||
@@ -3336,15 +3562,12 @@ public:
|
|||||||
std::binary_semaphore command_entered{0};
|
std::binary_semaphore command_entered{0};
|
||||||
std::binary_semaphore release_command{0};
|
std::binary_semaphore release_command{0};
|
||||||
std::atomic<bool> shutdown_complete{false};
|
std::atomic<bool> shutdown_complete{false};
|
||||||
std::atomic<bool> submission_lane_entered{false};
|
|
||||||
bool shutdown_owner_visible = false;
|
bool shutdown_owner_visible = false;
|
||||||
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
||||||
gpu.SendCommand([&] {
|
gpu.SendCommand([&] {
|
||||||
command_entered.release();
|
command_entered.release();
|
||||||
release_command.acquire();
|
release_command.acquire();
|
||||||
shutdown_owner_visible = &context.GetGpu() == &gpu;
|
shutdown_owner_visible = &context.GetGpu() == &gpu;
|
||||||
gpu.Done();
|
|
||||||
submission_lane_entered = true;
|
|
||||||
});
|
});
|
||||||
command_entered.acquire();
|
command_entered.acquire();
|
||||||
std::jthread shutdown_thread([&] {
|
std::jthread shutdown_thread([&] {
|
||||||
@@ -3360,8 +3583,7 @@ public:
|
|||||||
shutdown_thread.join();
|
shutdown_thread.join();
|
||||||
Require(
|
Require(
|
||||||
"GpuCommandLane", "owned shutdown completion",
|
"GpuCommandLane", "owned shutdown completion",
|
||||||
shutdown_complete.load() && submission_lane_entered.load() &&
|
shutdown_complete.load() && shutdown_owner_visible,
|
||||||
shutdown_owner_visible,
|
|
||||||
"GPU owner did not remain available while draining queued work");
|
"GPU owner did not remain available while draining queued work");
|
||||||
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
||||||
Require("GpuCommandLane", "unmap after shutdown",
|
Require("GpuCommandLane", "unmap after shutdown",
|
||||||
@@ -33981,12 +34203,15 @@ void CheckPm4Predication(RenderContext &renderer) {
|
|||||||
uint32_t wait, const void *source, bool skip) {
|
uint32_t wait, const void *source, bool skip) {
|
||||||
predicated = unconditional = 0;
|
predicated = unconditional = 0;
|
||||||
const auto packet = commands(op, condition, wait, source);
|
const auto packet = commands(op, condition, wait, source);
|
||||||
|
const auto tick = renderer.GetCommandScheduler().CurrentTick();
|
||||||
Pm4Execution execution;
|
Pm4Execution execution;
|
||||||
Require(name, stage,
|
Require(name, stage,
|
||||||
processor.Process(execution, packet) == Pm4ProcessResult::Complete &&
|
processor.Process(execution, packet) == Pm4ProcessResult::Complete &&
|
||||||
processor.ShouldSkipPredicatedPackets() == skip &&
|
processor.ShouldSkipPredicatedPackets() == skip &&
|
||||||
predicated == (skip ? 0u : 11u) && unconditional == 22,
|
predicated == (skip ? 0u : 11u) && unconditional == 22,
|
||||||
"predicate polarity, tagged packet execution, or packet consumption is wrong");
|
"predicate polarity, tagged packet execution, or packet consumption is wrong");
|
||||||
|
Require(name, stage, renderer.GetCommandScheduler().CurrentTick() == tick,
|
||||||
|
"predication unnecessarily submitted and drained the host queue");
|
||||||
};
|
};
|
||||||
struct Case {
|
struct Case {
|
||||||
uint64_t delta;
|
uint64_t delta;
|
||||||
@@ -35506,6 +35731,11 @@ int main(int argc, char **argv) {
|
|||||||
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
|
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (argc == 2 && std::strcmp(argv[1], "--suspend-point-only") == 0) {
|
||||||
|
VulkanHarness vulkan;
|
||||||
|
vulkan.CheckGpuSuspendPoint();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset());
|
RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset());
|
||||||
@@ -35915,6 +36145,7 @@ int main(int argc, char **argv) {
|
|||||||
for (const auto &test : graphics_tests) {
|
for (const auto &test : graphics_tests) {
|
||||||
RunGraphicsCase(&vulkan, test);
|
RunGraphicsCase(&vulkan, test);
|
||||||
}
|
}
|
||||||
|
vulkan.CheckGpuSuspendPoint();
|
||||||
vulkan.CheckGpuCommandLane();
|
vulkan.CheckGpuCommandLane();
|
||||||
if (skipped_device_checks) {
|
if (skipped_device_checks) {
|
||||||
std::printf(
|
std::printf(
|
||||||
|
|||||||
Reference in New Issue
Block a user