Fix unnecessary queue drains in predication and suspend points

This commit is contained in:
nmzik
2026-09-30 16:51:40 +04:00
parent 05057c9441
commit 840b9f5753
5 changed files with 289 additions and 110 deletions
+251 -20
View File
@@ -2402,6 +2402,232 @@ public:
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() {
EnsureRuntimeContext();
auto &context = Renderer();
@@ -2647,7 +2873,7 @@ public:
graphics_gate_entered.acquire();
live_graphics_commands[4] = 22;
graphics_gate_release.release();
gpu.Done();
gpu.WaitForIdle();
Require("GpuCommandLane", "borrowed graphics commands",
live_graphics_value == 22,
"graphics submission executed a copied PM4 stream");
@@ -2665,7 +2891,7 @@ public:
compute_gate_entered.acquire();
live_compute_commands[4] = 44;
compute_gate_release.release();
gpu.Done();
gpu.WaitForIdle();
Require("GpuCommandLane", "borrowed compute commands",
live_compute_value == 44,
"compute submission executed a copied PM4 stream");
@@ -2721,7 +2947,7 @@ public:
write_packet(no_interrupt_graphics_commands.data(),
&no_interrupt_graphics_value, 55);
gpu.Submit(no_interrupt_graphics_commands, {});
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
LibKernel::EventQueue::KernelEvent interrupt_event{};
@@ -2745,7 +2971,7 @@ public:
interrupt_gate_entered.acquire();
live_interrupt_commands[2] = 0;
interrupt_gate_release.release();
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
interrupt_count = 0;
@@ -2761,7 +2987,7 @@ public:
auto graphics_interrupt_commands = make_interrupt_packet(
1, 1, &graphics_interrupt_label, 0x11223344u, 0);
gpu.Submit(graphics_interrupt_commands, {});
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
interrupt_count = 0;
@@ -2787,7 +3013,7 @@ public:
compute_interrupt_gate_entered.acquire();
compute_interrupt_commands[2] |= 1u << 24u;
compute_interrupt_gate_release.release();
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
interrupt_count = 0;
@@ -2803,7 +3029,7 @@ public:
auto compute_clock_commands = make_interrupt_packet(
3, 1, &compute_clock_label, 0, 0x567u);
gpu.SubmitCompute(0x20, compute_clock_commands);
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
interrupt_count = 0;
@@ -2821,7 +3047,7 @@ public:
2, 2, &compute_done_label, compute_done_value, 0x678u);
compute_done_commands[1] = 0x62fu; // CS_DONE, shader-done index, no GCR action.
gpu.SubmitCompute(0x20, compute_done_commands);
gpu.Done();
gpu.WaitForIdle();
finish_gpu();
interrupt_count = 0;
@@ -2896,7 +3122,7 @@ public:
});
std::this_thread::yield();
}
gpu.Done();
gpu.WaitForIdle();
Require("GpuCommandLane", "packet-boundary command polling",
packet_marker_a_at_callback == 11 &&
packet_marker_b_at_callback == 0 && packet_marker_a == 11 &&
@@ -2934,21 +3160,21 @@ public:
uint32_t ordered_suffix = 0;
std::jthread ordered([&] {
ordered_started.release();
gpu.Done();
gpu.WaitForIdle();
ordered_suffix = suffix;
ordered_finished = true;
});
ordered_started.acquire();
gpu.SendCommandSync([&] {
Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(),
"submit done returned before a queued submission");
Require("GpuCommandLane", "guest queue idle fence", !ordered_finished.load(),
"idle fence returned before a queued submission");
label = 1;
});
ordered.join();
Require("GpuCommandLane", "ordered completion",
prefix == 11 && suffix == 22 && ordered_suffix == 22 &&
ordered_finished.load(),
"submit done did not drain prior PM4 work");
"idle fence did not drain prior PM4 work");
auto &resources = context;
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
@@ -2971,7 +3197,7 @@ public:
unmap_complete.acquire();
}
unmap_thread.join();
gpu.Done();
gpu.WaitForIdle();
Require("GpuCommandLane", "unmap queue progress",
unmap_returned &&
!resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
@@ -3098,7 +3324,7 @@ public:
dma_cursor == dma_commands.size(),
"DMA_DATA packet stream has the wrong size");
gpu.Submit(dma_commands, {});
gpu.Done();
gpu.WaitForIdle();
constexpr uint32_t clean_fill_value = 0xdecafbad;
gpu.SendCommandSync([&] {
auto &buffer_cache = resources.GetBufferCache();
@@ -3336,15 +3562,12 @@ public:
std::binary_semaphore command_entered{0};
std::binary_semaphore release_command{0};
std::atomic<bool> shutdown_complete{false};
std::atomic<bool> submission_lane_entered{false};
bool shutdown_owner_visible = false;
resources.MapMemory(empty_unmap_base, empty_unmap_size);
gpu.SendCommand([&] {
command_entered.release();
release_command.acquire();
shutdown_owner_visible = &context.GetGpu() == &gpu;
gpu.Done();
submission_lane_entered = true;
});
command_entered.acquire();
std::jthread shutdown_thread([&] {
@@ -3360,8 +3583,7 @@ public:
shutdown_thread.join();
Require(
"GpuCommandLane", "owned shutdown completion",
shutdown_complete.load() && submission_lane_entered.load() &&
shutdown_owner_visible,
shutdown_complete.load() && shutdown_owner_visible,
"GPU owner did not remain available while draining queued work");
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
Require("GpuCommandLane", "unmap after shutdown",
@@ -33981,12 +34203,15 @@ void CheckPm4Predication(RenderContext &renderer) {
uint32_t wait, const void *source, bool skip) {
predicated = unconditional = 0;
const auto packet = commands(op, condition, wait, source);
const auto tick = renderer.GetCommandScheduler().CurrentTick();
Pm4Execution execution;
Require(name, stage,
processor.Process(execution, packet) == Pm4ProcessResult::Complete &&
processor.ShouldSkipPredicatedPackets() == skip &&
predicated == (skip ? 0u : 11u) && unconditional == 22,
"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 {
uint64_t delta;
@@ -35506,6 +35731,11 @@ int main(int argc, char **argv) {
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
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) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset());
@@ -35915,6 +36145,7 @@ int main(int argc, char **argv) {
for (const auto &test : graphics_tests) {
RunGraphicsCase(&vulkan, test);
}
vulkan.CheckGpuSuspendPoint();
vulkan.CheckGpuCommandLane();
if (skipped_device_checks) {
std::printf(