1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <memory>
#include <vector>
#include "common/assert.h"
#include "core/core.h"
#include "video_core/control/channel_state.h"
#include "video_core/control/scheduler.h"
#include "video_core/gpu.h"
#include "video_core/gpu_split.h"
namespace Tegra::Control {
void Scheduler::BindChannel(GPU& gpu, s32 bind_id) {
// With the GPU thread split the rasterizer's channel switches with the methods around it, on
// the back thread (deferred outside the lock: the back takes it to bind).
if (auto& split = VideoCommon::GpuSplit::Get(); split.Active()) {
split.Defer([this, gpu_ptr = &gpu, bind_id] {
std::scoped_lock lk{scheduling_guard};
gpu_ptr->BindChannel(bind_id);
});
return;
}
std::scoped_lock lk{scheduling_guard};
gpu.BindChannel(bind_id);
}
void Scheduler::Push(GPU& gpu, s32 channel, CommandList&& entries) {
std::shared_ptr<ChannelState> channel_state;
{
std::unique_lock lk(scheduling_guard);
auto it = channels.find(channel);
ASSERT(it != channels.end());
channel_state = it->second;
}
BindChannel(gpu, channel_state->bind_id);
// Process commands outside the lock to reduce contention.
// Multiple channels can prepare their commands in parallel.
channel_state->payload->dma_pusher.Push(std::move(entries));
channel_state->payload->dma_pusher.DispatchCalls();
if (channel_state->payload->dma_pusher.HasPendingAcquire() &&
std::find(pending_channels.begin(), pending_channels.end(), channel_state) ==
pending_channels.end()) {
pending_channels.push_back(channel_state);
}
if (!pending_channels.empty()) {
ResumePending(gpu);
}
}
void Scheduler::ResumePending(GPU& gpu) {
// Running one channel can release the semaphore another waits for: retry until none moves.
for (bool progress = true; progress;) {
progress = false;
for (const auto& state : pending_channels) {
auto& dma_pusher = state->payload->dma_pusher;
// Powered off (shutting down), DispatchCalls returns at once and the acquire stays
// pending: resuming it again counted as progress forever and the GPU thread never
// reached its stop request (closing the game hung).
if (!dma_pusher.system.IsPoweredOn()) {
return;
}
if (!dma_pusher.HasPendingAcquire() || !dma_pusher.CanResumeAcquire()) {
continue;
}
BindChannel(gpu, state->bind_id);
dma_pusher.DispatchCalls();
progress = true;
}
}
std::erase_if(pending_channels, [](const auto& state) {
return !state->payload->dma_pusher.HasPendingAcquire();
});
}
void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {
s32 channel = new_channel->bind_id;
std::unique_lock lk(scheduling_guard);
channels.emplace(channel, new_channel);
}
} // namespace Tegra::Control
|