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(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(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 #include #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 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 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 + +#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(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_