From b567b9e9cc90819e399dace09d3b9073cc27f289 Mon Sep 17 00:00:00 2001
From: Triang3l <triang3l@yandex.ru>
Date: Sun, 29 Jul 2018 00:56:26 +0300
Subject: [PATCH] [D3D12] Upload buffer pool class

---
 src/xenia/gpu/d3d12/shared_memory.cc | 139 +++-----------------
 src/xenia/gpu/d3d12/shared_memory.h  |  15 +--
 src/xenia/ui/d3d12/pools.cc          | 183 +++++++++++++++++++++++++++
 src/xenia/ui/d3d12/pools.h           |  68 ++++++++++
 4 files changed, 273 insertions(+), 132 deletions(-)
 create mode 100644 src/xenia/ui/d3d12/pools.cc
 create mode 100644 src/xenia/ui/d3d12/pools.h

diff --git a/src/xenia/gpu/d3d12/shared_memory.cc b/src/xenia/gpu/d3d12/shared_memory.cc
index f89d6ca75..1f8e72f99 100644
--- a/src/xenia/gpu/d3d12/shared_memory.cc
+++ b/src/xenia/gpu/d3d12/shared_memory.cc
@@ -75,9 +75,8 @@ bool SharedMemory::Initialize() {
               watches_triggered_l2_.size() * sizeof(uint64_t));
 
   std::memset(upload_pages_.data(), 0, upload_pages_.size() * sizeof(uint64_t));
-  upload_buffer_available_first_ = nullptr;
-  upload_buffer_submitted_first_ = nullptr;
-  upload_buffer_submitted_last_ = nullptr;
+  upload_buffer_pool_ =
+      std::make_unique<ui::d3d12::UploadBufferPool>(context_, 4 * 1024 * 1024);
 
   memory_->SetGlobalPhysicalAccessWatch(WatchCallbackThunk, this);
 
@@ -87,18 +86,7 @@ bool SharedMemory::Initialize() {
 void SharedMemory::Shutdown() {
   memory_->SetGlobalPhysicalAccessWatch(nullptr, nullptr);
 
-  while (upload_buffer_available_first_ != nullptr) {
-    auto upload_buffer_next = upload_buffer_available_first_->next;
-    upload_buffer_available_first_->buffer->Release();
-    delete upload_buffer_available_first_;
-    upload_buffer_available_first_ = upload_buffer_next;
-  }
-  while (upload_buffer_submitted_first_ != nullptr) {
-    auto upload_buffer_next = upload_buffer_submitted_first_->next;
-    upload_buffer_submitted_first_->buffer->Release();
-    delete upload_buffer_submitted_first_;
-    upload_buffer_submitted_first_ = upload_buffer_next;
-  }
+  upload_buffer_pool_.reset();
 
   // First free the buffer to detach it from the heaps.
   if (buffer_ != nullptr) {
@@ -132,20 +120,7 @@ void SharedMemory::BeginFrame() {
   }
   watch_mutex_.unlock();
 
-  // Make processed upload buffers available.
-  uint64_t last_completed_frame = context_->GetLastCompletedFrame();
-  while (upload_buffer_submitted_first_ != nullptr) {
-    auto upload_buffer = upload_buffer_submitted_first_;
-    if (upload_buffer->submit_frame > last_completed_frame) {
-      break;
-    }
-    upload_buffer_submitted_first_ = upload_buffer->next;
-    upload_buffer->next = upload_buffer_available_first_;
-    upload_buffer_available_first_ = upload_buffer;
-  }
-  if (upload_buffer_submitted_first_ == nullptr) {
-    upload_buffer_submitted_last_ = nullptr;
-  }
+  upload_buffer_pool_->BeginFrame();
 
   heap_creation_failed_ = false;
 
@@ -164,12 +139,7 @@ bool SharedMemory::EndFrame(ID3D12GraphicsCommandList* command_list_setup,
   auto device = context_->GetD3D12Provider()->GetDevice();
 
   // Write ranges to upload buffers and submit them.
-  const uint32_t upload_buffer_capacity = kUploadBufferSize >> page_size_log2_;
-  assert_true(upload_buffer_capacity > 0);
-  uint32_t upload_end = 0;
-  void* upload_buffer_mapping = nullptr;
-  uint32_t upload_buffer_written = 0;
-  uint32_t upload_range_start = 0, upload_range_length;
+  uint32_t upload_end = 0, upload_range_start = 0, upload_range_length;
   while ((upload_range_start =
               NextUploadRange(upload_end, upload_range_length)) != UINT_MAX) {
     /* XELOGGPU(
@@ -177,103 +147,32 @@ bool SharedMemory::EndFrame(ID3D12GraphicsCommandList* command_list_setup,
         upload_range_start << page_size_log2_,
         ((upload_range_start + upload_range_length) << page_size_log2_) - 1); */
     while (upload_range_length > 0) {
+      ID3D12Resource* upload_buffer;
+      uint32_t upload_buffer_offset, upload_buffer_size;
+      uint8_t* upload_buffer_mapping = upload_buffer_pool_->RequestPartial(
+          upload_range_length << page_size_log2_, upload_buffer,
+          upload_buffer_offset, upload_buffer_size);
       if (upload_buffer_mapping == nullptr) {
-        // Create a completely new upload buffer if the available pool is empty.
-        if (upload_buffer_available_first_ == nullptr) {
-          D3D12_HEAP_PROPERTIES upload_buffer_heap_properties = {};
-          upload_buffer_heap_properties.Type = D3D12_HEAP_TYPE_UPLOAD;
-          D3D12_RESOURCE_DESC upload_buffer_desc;
-          upload_buffer_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
-          upload_buffer_desc.Alignment = 0;
-          upload_buffer_desc.Width = kUploadBufferSize;
-          upload_buffer_desc.Height = 1;
-          upload_buffer_desc.DepthOrArraySize = 1;
-          upload_buffer_desc.MipLevels = 1;
-          upload_buffer_desc.Format = DXGI_FORMAT_UNKNOWN;
-          upload_buffer_desc.SampleDesc.Count = 1;
-          upload_buffer_desc.SampleDesc.Quality = 0;
-          upload_buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
-          upload_buffer_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
-          ID3D12Resource* upload_buffer_resource;
-          if (FAILED(device->CreateCommittedResource(
-                  &upload_buffer_heap_properties, D3D12_HEAP_FLAG_NONE,
-                  &upload_buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ,
-                  nullptr, IID_PPV_ARGS(&upload_buffer_resource)))) {
-            XELOGE("Shared memory: Failed to create an upload buffer");
-            break;
-          }
-          upload_buffer_available_first_ = new UploadBuffer;
-          upload_buffer_available_first_->buffer = upload_buffer_resource;
-          upload_buffer_available_first_->next = nullptr;
-        }
-        // New buffer, need to map it.
-        D3D12_RANGE upload_buffer_read_range;
-        upload_buffer_read_range.Begin = 0;
-        upload_buffer_read_range.End = 0;
-        if (FAILED(upload_buffer_available_first_->buffer->Map(
-                0, &upload_buffer_read_range, &upload_buffer_mapping))) {
-          XELOGE("Shared memory: Failed to map an upload buffer");
-          break;
-        }
+        XELOGE("Shared memory: Failed to get an upload buffer");
+        break;
       }
-
-      // Upload the portion we can upload.
-      uint32_t upload_write_length = std::min(
-          upload_range_length, upload_buffer_capacity - upload_buffer_written);
       std::memcpy(
-          reinterpret_cast<uint8_t*>(upload_buffer_mapping) +
-              (upload_buffer_written << page_size_log2_),
+          upload_buffer_mapping,
           memory_->TranslatePhysical(upload_range_start << page_size_log2_),
-          upload_write_length << page_size_log2_);
+          upload_buffer_size);
       command_list_setup->CopyBufferRegion(
-          buffer_, upload_range_start << page_size_log2_,
-          upload_buffer_available_first_->buffer,
-          upload_buffer_written << page_size_log2_,
-          upload_write_length << page_size_log2_);
-      upload_buffer_written += upload_write_length;
-      upload_range_start += upload_write_length;
-      upload_range_length -= upload_write_length;
+          buffer_, upload_range_start << page_size_log2_, upload_buffer,
+          upload_buffer_offset, upload_buffer_size);
+      upload_range_start += upload_buffer_size >> page_size_log2_;
+      upload_range_length -= upload_buffer_size >> page_size_log2_;
       upload_end = upload_range_start;
-
-      // Check if we are done with this buffer.
-      if (upload_buffer_written == upload_buffer_capacity) {
-        auto upload_buffer = upload_buffer_available_first_;
-        upload_buffer->buffer->Unmap(0, nullptr);
-        upload_buffer_mapping = nullptr;
-        upload_buffer_available_first_ = upload_buffer->next;
-        upload_buffer->next = nullptr;
-        upload_buffer->submit_frame = current_frame;
-        if (upload_buffer_submitted_last_ != nullptr) {
-          upload_buffer_submitted_last_->next = upload_buffer;
-        } else {
-          upload_buffer_submitted_first_ = upload_buffer;
-        }
-        upload_buffer_submitted_last_ = upload_buffer;
-        upload_buffer_written = 0;
-      }
     }
     if (upload_range_length > 0) {
       // Buffer creation or mapping failed.
       break;
     }
   }
-  // Mark the last upload buffer as submitted if anything was uploaded from it,
-  // also unmap it.
-  if (upload_buffer_mapping != nullptr) {
-    upload_buffer_available_first_->buffer->Unmap(0, nullptr);
-  }
-  if (upload_buffer_written > 0) {
-    auto upload_buffer = upload_buffer_available_first_;
-    upload_buffer_available_first_ = upload_buffer->next;
-    upload_buffer->next = nullptr;
-    upload_buffer->submit_frame = current_frame;
-    if (upload_buffer_submitted_last_ != nullptr) {
-      upload_buffer_submitted_last_->next = upload_buffer;
-    } else {
-      upload_buffer_submitted_first_ = upload_buffer;
-    }
-    upload_buffer_submitted_last_ = upload_buffer;
-  }
+  upload_buffer_pool_->EndFrame();
 
   // Protect the uploaded ranges.
   // TODO(Triang3l): Add L2 or store ranges in a list - this may hold the mutex
diff --git a/src/xenia/gpu/d3d12/shared_memory.h b/src/xenia/gpu/d3d12/shared_memory.h
index 3eec30634..b0b904e92 100644
--- a/src/xenia/gpu/d3d12/shared_memory.h
+++ b/src/xenia/gpu/d3d12/shared_memory.h
@@ -10,11 +10,13 @@
 #ifndef XENIA_GPU_D3D12_SHARED_MEMORY_H_
 #define XENIA_GPU_D3D12_SHARED_MEMORY_H_
 
+#include <memory>
 #include <mutex>
 
 #include "xenia/memory.h"
 #include "xenia/ui/d3d12/d3d12_api.h"
 #include "xenia/ui/d3d12/d3d12_context.h"
+#include "xenia/ui/d3d12/pools.h"
 
 namespace xe {
 namespace gpu {
@@ -100,19 +102,8 @@ class SharedMemory {
 
   // Pages that need to be uploaded in this frame (that are used but modified).
   std::vector<uint64_t> upload_pages_;
-  static constexpr uint32_t kUploadBufferSize = 4 * 1024 * 1024;
-  struct UploadBuffer {
-    ID3D12Resource* buffer;
-    // Next free or submitted upload buffer.
-    UploadBuffer* next;
-    // When this buffer was submitted (only valid for submitted buffers).
-    uint64_t submit_frame;
-  };
-  // Buffers are moved to available in BeginFrame and to submitted in EndFrame.
-  UploadBuffer* upload_buffer_submitted_first_ = nullptr;
-  UploadBuffer* upload_buffer_submitted_last_ = nullptr;
-  UploadBuffer* upload_buffer_available_first_ = nullptr;
   uint32_t NextUploadRange(uint32_t search_start, uint32_t& length) const;
+  std::unique_ptr<ui::d3d12::UploadBufferPool> upload_buffer_pool_ = nullptr;
 
   void TransitionBuffer(D3D12_RESOURCE_STATES new_state,
                         ID3D12GraphicsCommandList* command_list);
diff --git a/src/xenia/ui/d3d12/pools.cc b/src/xenia/ui/d3d12/pools.cc
new file mode 100644
index 000000000..9805e5b6f
--- /dev/null
+++ b/src/xenia/ui/d3d12/pools.cc
@@ -0,0 +1,183 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project                                 *
+ ******************************************************************************
+ * Copyright 2018 Ben Vanik. All rights reserved.                             *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#include "xenia/ui/d3d12/pools.h"
+
+#include <algorithm>
+
+#include "xenia/base/assert.h"
+#include "xenia/base/logging.h"
+
+namespace xe {
+namespace ui {
+namespace d3d12 {
+
+UploadBufferPool::UploadBufferPool(D3D12Context* context, uint32_t page_size)
+    : context_(context), page_size_(page_size) {}
+
+UploadBufferPool::~UploadBufferPool() { ClearCache(); }
+
+void UploadBufferPool::BeginFrame() {
+  // Recycle submitted pages not used by the GPU anymore.
+  uint64_t last_completed_frame = context_->GetLastCompletedFrame();
+  while (sent_first_ != nullptr) {
+    auto page = sent_first_;
+    if (page->frame_sent > last_completed_frame) {
+      break;
+    }
+    sent_first_ = page->next;
+    page->next = unsent_;
+    unsent_ = page;
+  }
+  if (sent_first_ == nullptr) {
+    sent_last_ = nullptr;
+  }
+
+  // Try to create new pages again in this frame if failed in the previous.
+  creation_failed_ = false;
+}
+
+void UploadBufferPool::EndFrame() {
+  // If something is written to the current page, mark it as submitted.
+  EndPage();
+}
+
+void UploadBufferPool::ClearCache() {
+  assert(current_size_ == 0);
+  while (unsent_ != nullptr) {
+    auto next = unsent_->next;
+    unsent_->buffer->Release();
+    delete unsent_;
+    unsent_ = next;
+  }
+  while (sent_first_ != nullptr) {
+    auto next = sent_first_->next;
+    sent_first_->buffer->Release();
+    delete sent_first_;
+    sent_first_ = next;
+  }
+  sent_last_ = nullptr;
+}
+
+uint8_t* UploadBufferPool::RequestFull(uint32_t size,
+                                       ID3D12Resource*& buffer_out,
+                                       uint32_t& offset_out) {
+  assert_true(size != 0 && size <= page_size_);
+  if (size == 0 || size > page_size_) {
+    return nullptr;
+  }
+  if (page_size_ - current_size_ < size || current_mapping_ == nullptr) {
+    // Start a new page if can't fit all the bytes or don't have an open page.
+    if (!BeginNextPage()) {
+      return nullptr;
+    }
+  }
+  buffer_out = unsent_->buffer;
+  offset_out = current_size_;
+  uint8_t* mapping = current_mapping_ + current_size_;
+  current_size_ += size;
+  return mapping;
+}
+
+uint8_t* UploadBufferPool::RequestPartial(uint32_t size,
+                                          ID3D12Resource*& buffer_out,
+                                          uint32_t& offset_out,
+                                          uint32_t& size_out) {
+  assert_true(size != 0);
+  if (size == 0) {
+    return nullptr;
+  }
+  if (current_size_ == page_size_ || current_mapping_ == nullptr) {
+    // Start a new page if can't fit any bytes or don't have an open page.
+    if (!BeginNextPage()) {
+      return nullptr;
+    }
+  }
+  size = std::min(size, page_size_ - current_size_);
+  buffer_out = unsent_->buffer;
+  offset_out = current_size_;
+  size_out = size;
+  uint8_t* mapping = current_mapping_ + current_size_;
+  current_size_ += size;
+  return mapping;
+}
+
+void UploadBufferPool::EndPage() {
+  if (current_mapping_ != nullptr) {
+    D3D12_RANGE written_range;
+    written_range.Begin = 0;
+    written_range.End = current_size_;
+    unsent_->buffer->Unmap(0, &written_range);
+    current_mapping_ = nullptr;
+  }
+  if (current_size_ != 0) {
+    auto buffer = unsent_;
+    buffer->frame_sent = context_->GetCurrentFrame();
+    unsent_ = buffer->next;
+    buffer->next = nullptr;
+    if (sent_last_ != nullptr) {
+      sent_last_->next = buffer;
+    } else {
+      sent_first_ = buffer;
+    }
+    sent_last_ = buffer;
+    current_size_ = 0;
+  }
+}
+
+bool UploadBufferPool::BeginNextPage() {
+  EndPage();
+
+  if (unsent_ == nullptr) {
+    auto device = context_->GetD3D12Provider()->GetDevice();
+    D3D12_HEAP_PROPERTIES heap_properties = {};
+    heap_properties.Type = D3D12_HEAP_TYPE_UPLOAD;
+    D3D12_RESOURCE_DESC buffer_desc;
+    buffer_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+    buffer_desc.Alignment = 0;
+    buffer_desc.Width = page_size_;
+    buffer_desc.Height = 1;
+    buffer_desc.DepthOrArraySize = 1;
+    buffer_desc.MipLevels = 1;
+    buffer_desc.Format = DXGI_FORMAT_UNKNOWN;
+    buffer_desc.SampleDesc.Count = 1;
+    buffer_desc.SampleDesc.Quality = 0;
+    buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+    buffer_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
+    ID3D12Resource* buffer_resource;
+    if (FAILED(device->CreateCommittedResource(
+            &heap_properties, D3D12_HEAP_FLAG_NONE, &buffer_desc,
+            D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
+            IID_PPV_ARGS(&buffer_resource)))) {
+      XELOGE("Failed to create a D3D upload buffer with %u bytes", page_size_);
+      creation_failed_ = true;
+      return false;
+    }
+    unsent_ = new UploadBuffer;
+    unsent_->buffer = buffer_resource;
+    unsent_->next = nullptr;
+  }
+
+  D3D12_RANGE read_range;
+  read_range.Begin = 0;
+  read_range.End = 0;
+  void* mapping;
+  if (FAILED(unsent_->buffer->Map(0, &read_range, &mapping))) {
+    XELOGE("Failed to map a D3D upload buffer with %u bytes", page_size_);
+    creation_failed_ = true;
+    return false;
+  }
+  current_mapping_ = reinterpret_cast<uint8_t*>(mapping);
+
+  return true;
+}
+
+}  // namespace d3d12
+}  // namespace ui
+}  // namespace xe
diff --git a/src/xenia/ui/d3d12/pools.h b/src/xenia/ui/d3d12/pools.h
new file mode 100644
index 000000000..9cfa6196b
--- /dev/null
+++ b/src/xenia/ui/d3d12/pools.h
@@ -0,0 +1,68 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project                                 *
+ ******************************************************************************
+ * Copyright 2018 Ben Vanik. All rights reserved.                             *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_UI_D3D12_POOLS_H_
+#define XENIA_UI_D3D12_POOLS_H_
+
+#include "xenia/ui/d3d12/d3d12_api.h"
+#include "xenia/ui/d3d12/d3d12_context.h"
+
+namespace xe {
+namespace ui {
+namespace d3d12 {
+
+class UploadBufferPool {
+ public:
+  UploadBufferPool(D3D12Context* context, uint32_t page_size);
+  ~UploadBufferPool();
+
+  void BeginFrame();
+  void EndFrame();
+  void ClearCache();
+
+  // Request to write data in a single piece, creating a new page if the current
+  // one doesn't have enough free space.
+  uint8_t* RequestFull(uint32_t size, ID3D12Resource*& buffer_out,
+                       uint32_t& offset_out);
+  // Request to write data in multiple parts, filling the buffer entirely.
+  uint8_t* RequestPartial(uint32_t size, ID3D12Resource*& buffer_out,
+                          uint32_t& offset_out, uint32_t& size_out);
+
+ private:
+  D3D12Context* context_;
+  uint32_t page_size_;
+
+  void EndPage();
+  bool BeginNextPage();
+
+  struct UploadBuffer {
+    ID3D12Resource* buffer;
+    UploadBuffer* next;
+    uint64_t frame_sent;
+  };
+
+  // A list of unsent buffers, with the first one being the current.
+  UploadBuffer* unsent_ = nullptr;
+  // A list of sent buffers, moved to unsent in the beginning of a frame.
+  UploadBuffer* sent_first_ = nullptr;
+  UploadBuffer* sent_last_ = nullptr;
+
+  uint32_t current_size_ = 0;
+  uint8_t* current_mapping_ = nullptr;
+
+  // Reset in the beginning of a frame - don't try and fail to create a new
+  // buffer if failed to create one in the current frame.
+  bool creation_failed_ = false;
+};
+
+}  // namespace d3d12
+}  // namespace ui
+}  // namespace xe
+
+#endif  // XENIA_UI_D3D12_POOLS_H_