diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index fab7a12e4..67d82c2bf 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include "common/alignment.h"
+#include "common/scope_exit.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/vi/vi.h"
@@ -721,8 +722,30 @@ Layer& NVFlinger::GetLayer(u64 display_id, u64 layer_id) {
 
 void NVFlinger::Compose() {
     for (auto& display : displays) {
-        // TODO(Subv): Gather the surfaces and forward them to the GPU for drawing.
-        display.vsync_event->Signal();
+        // Trigger vsync for this display at the end of drawing
+        SCOPE_EXIT({ display.vsync_event->Signal(); });
+
+        // Don't do anything for displays without layers.
+        if (display.layers.empty())
+            continue;
+
+        // TODO(Subv): Support more than 1 layer.
+        ASSERT_MSG(display.layers.size() == 1, "Max 1 layer per display is supported");
+
+        Layer& layer = display.layers[0];
+        auto& buffer_queue = layer.buffer_queue;
+
+        // Search for a queued buffer and acquire it
+        auto buffer = buffer_queue->AcquireBuffer();
+
+        if (buffer == boost::none) {
+            // There was no queued buffer to draw.
+            continue;
+        }
+
+        // TODO(Subv): Send the buffer to the GPU for drawing.
+
+        buffer_queue->ReleaseBuffer(buffer->slot);
     }
 }
 
@@ -732,7 +755,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
     Buffer buffer{};
     buffer.slot = slot;
     buffer.igbp_buffer = igbp_buffer;
-    buffer.status = Buffer::Status::Queued;
+    buffer.status = Buffer::Status::Free;
 
     LOG_WARNING(Service, "Adding graphics buffer %u", slot);
 
@@ -741,8 +764,9 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, IGBPBuffer& igbp_buffer) {
 
 u32 BufferQueue::DequeueBuffer(u32 pixel_format, u32 width, u32 height) {
     auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
-        // Only consider enqueued buffers
-        if (buffer.status != Buffer::Status::Queued)
+        // Only consider free buffers. Buffers become free once again after they've been Acquired
+        // and Released by the compositor, see the NVFlinger::Compose method.
+        if (buffer.status != Buffer::Status::Free)
             return false;
 
         // Make sure that the parameters match.
@@ -772,6 +796,24 @@ void BufferQueue::QueueBuffer(u32 slot) {
     itr->status = Buffer::Status::Queued;
 }
 
+boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
+    auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
+        return buffer.status == Buffer::Status::Queued;
+    });
+    if (itr == queue.end())
+        return boost::none;
+    itr->status = Buffer::Status::Acquired;
+    return *itr;
+}
+
+void BufferQueue::ReleaseBuffer(u32 slot) {
+    auto itr = std::find_if(queue.begin(), queue.end(),
+                            [&](const Buffer& buffer) { return buffer.slot == slot; });
+    ASSERT(itr != queue.end());
+    ASSERT(itr->status == Buffer::Status::Acquired);
+    itr->status = Buffer::Status::Free;
+}
+
 Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {}
 
 Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index 1bd8f7472..576c4ce32 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <memory>
+#include <boost/optional.hpp>
 #include "core/hle/kernel/event.h"
 #include "core/hle/service/service.h"
 
@@ -34,10 +35,20 @@ public:
     BufferQueue(u32 id, u64 layer_id);
     ~BufferQueue() = default;
 
+    struct Buffer {
+        enum class Status { Free = 0, Queued = 1, Dequeued = 2, Acquired = 3 };
+
+        u32 slot;
+        Status status = Status::Free;
+        IGBPBuffer igbp_buffer;
+    };
+
     void SetPreallocatedBuffer(u32 slot, IGBPBuffer& buffer);
     u32 DequeueBuffer(u32 pixel_format, u32 width, u32 height);
     const IGBPBuffer& RequestBuffer(u32 slot) const;
     void QueueBuffer(u32 slot);
+    boost::optional<const Buffer&> AcquireBuffer();
+    void ReleaseBuffer(u32 slot);
 
     u32 GetId() const {
         return id;
@@ -47,14 +58,6 @@ private:
     u32 id;
     u64 layer_id;
 
-    struct Buffer {
-        enum class Status { None = 0, Queued = 1, Dequeued = 2 };
-
-        u32 slot;
-        Status status = Status::None;
-        IGBPBuffer igbp_buffer;
-    };
-
     std::vector<Buffer> queue;
 };