service: nvflinger: Improve synchronization for BufferQueue.

- Use proper mechanisms for blocking on DequeueBuffer.
- Ensure service thread terminates on emulation Shutdown.
This commit is contained in:
bunnei 2020-12-16 21:09:06 -08:00
parent bea51d948d
commit 6433b1dfd6
5 changed files with 72 additions and 19 deletions

View file

@ -25,7 +25,12 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
ASSERT(slot < buffer_slots); ASSERT(slot < buffer_slots);
LOG_WARNING(Service, "Adding graphics buffer {}", slot); LOG_WARNING(Service, "Adding graphics buffer {}", slot);
{
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot); free_buffers.push_back(slot);
}
condition.notify_one();
buffers[slot] = { buffers[slot] = {
.slot = slot, .slot = slot,
.status = Buffer::Status::Free, .status = Buffer::Status::Free,
@ -41,10 +46,20 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
u32 height) { u32 height) {
// Wait for first request before trying to dequeue
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; });
}
if (free_buffers.empty()) { if (!is_connect) {
// Buffer was disconnected while the thread was blocked, this is most likely due to
// emulation being stopped
return std::nullopt; return std::nullopt;
} }
std::unique_lock lock{queue_mutex};
auto f_itr = free_buffers.begin(); auto f_itr = free_buffers.begin();
auto slot = buffers.size(); auto slot = buffers.size();
@ -97,7 +112,11 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult
buffers[slot].multi_fence = multi_fence; buffers[slot].multi_fence = multi_fence;
buffers[slot].swap_interval = 0; buffers[slot].swap_interval = 0;
{
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot); free_buffers.push_back(slot);
}
condition.notify_one();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
} }
@ -127,15 +146,28 @@ void BufferQueue::ReleaseBuffer(u32 slot) {
ASSERT(buffers[slot].slot == slot); ASSERT(buffers[slot].slot == slot);
buffers[slot].status = Buffer::Status::Free; buffers[slot].status = Buffer::Status::Free;
{
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot); free_buffers.push_back(slot);
}
condition.notify_one();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
} }
void BufferQueue::Connect() {
queue_sequence.clear();
id = 1;
layer_id = 1;
is_connect = true;
}
void BufferQueue::Disconnect() { void BufferQueue::Disconnect() {
buffers.fill({}); buffers.fill({});
queue_sequence.clear(); queue_sequence.clear();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
is_connect = false;
condition.notify_one();
} }
u32 BufferQueue::Query(QueryType type) { u32 BufferQueue::Query(QueryType type) {

View file

@ -4,7 +4,9 @@
#pragma once #pragma once
#include <condition_variable>
#include <list> #include <list>
#include <mutex>
#include <optional> #include <optional>
#include <vector> #include <vector>
@ -99,6 +101,7 @@ public:
void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence); void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer(); std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot); void ReleaseBuffer(u32 slot);
void Connect();
void Disconnect(); void Disconnect();
u32 Query(QueryType type); u32 Query(QueryType type);
@ -106,18 +109,28 @@ public:
return id; return id;
} }
bool IsConnected() const {
return is_connect;
}
std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const; std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const;
std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const; std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const;
private: private:
u32 id; BufferQueue(const BufferQueue&) = delete;
u64 layer_id;
u32 id{};
u64 layer_id{};
std::atomic_bool is_connect{};
std::list<u32> free_buffers; std::list<u32> free_buffers;
std::array<Buffer, buffer_slots> buffers; std::array<Buffer, buffer_slots> buffers;
std::list<u32> queue_sequence; std::list<u32> queue_sequence;
Kernel::EventPair buffer_wait_event; Kernel::EventPair buffer_wait_event;
std::mutex queue_mutex;
std::condition_variable condition;
}; };
} // namespace Service::NVFlinger } // namespace Service::NVFlinger

View file

@ -88,6 +88,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
} }
NVFlinger::~NVFlinger() { NVFlinger::~NVFlinger() {
for (auto& buffer_queue : buffer_queues) {
buffer_queue->Disconnect();
}
if (system.IsMulticore()) { if (system.IsMulticore()) {
is_running = false; is_running = false;
wait_event->Set(); wait_event->Set();
@ -132,8 +136,9 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
const u64 layer_id = next_layer_id++; const u64 layer_id = next_layer_id++;
const u32 buffer_queue_id = next_buffer_queue_id++; const u32 buffer_queue_id = next_buffer_queue_id++;
buffer_queues.emplace_back(system.Kernel(), buffer_queue_id, layer_id); buffer_queues.emplace_back(
display->CreateLayer(layer_id, buffer_queues.back()); std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id));
display->CreateLayer(layer_id, *buffer_queues.back());
return layer_id; return layer_id;
} }
@ -170,13 +175,13 @@ std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id)
BufferQueue* NVFlinger::FindBufferQueue(u32 id) { BufferQueue* NVFlinger::FindBufferQueue(u32 id) {
const auto guard = Lock(); const auto guard = Lock();
const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
[id](const auto& queue) { return queue.GetId() == id; }); [id](const auto& queue) { return queue->GetId() == id; });
if (itr == buffer_queues.end()) { if (itr == buffer_queues.end()) {
return nullptr; return nullptr;
} }
return &*itr; return itr->get();
} }
VI::Display* NVFlinger::FindDisplay(u64 display_id) { VI::Display* NVFlinger::FindDisplay(u64 display_id) {

View file

@ -107,7 +107,7 @@ private:
std::shared_ptr<Nvidia::Module> nvdrv; std::shared_ptr<Nvidia::Module> nvdrv;
std::vector<VI::Display> displays; std::vector<VI::Display> displays;
std::vector<BufferQueue> buffer_queues; std::vector<std::unique_ptr<BufferQueue>> buffer_queues;
/// Id to use for the next layer that is created, this counter is shared among all displays. /// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1; u64 next_layer_id = 1;

View file

@ -544,6 +544,12 @@ private:
Settings::values.resolution_factor.GetValue()), Settings::values.resolution_factor.GetValue()),
static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) *
Settings::values.resolution_factor.GetValue())}; Settings::values.resolution_factor.GetValue())};
{
auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
buffer_queue.Connect();
}
ctx.WriteBuffer(response.Serialize()); ctx.WriteBuffer(response.Serialize());
break; break;
} }
@ -565,18 +571,15 @@ private:
const u32 width{request.data.width}; const u32 width{request.data.width};
const u32 height{request.data.height}; const u32 height{request.data.height};
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> result;
while (!result) {
auto& buffer_queue = *nv_flinger.FindBufferQueue(id); auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
result = buffer_queue.DequeueBuffer(width, height); do {
if (auto result = buffer_queue.DequeueBuffer(width, height); result) {
if (result) {
// Buffer is available // Buffer is available
IGBPDequeueBufferResponseParcel response{result->first, *result->second}; IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize()); ctx.WriteBuffer(response.Serialize());
break;
} }
} } while (buffer_queue.IsConnected());
break; break;
} }