[UI] UploadBufferPool common code

This commit is contained in:
Triang3l
2020-09-17 21:37:44 +03:00
parent 2cebd3cabe
commit 189a38826f
18 changed files with 631 additions and 429 deletions

View File

@@ -0,0 +1,137 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/ui/d3d12/d3d12_util.h"
namespace xe {
namespace ui {
namespace d3d12 {
D3D12DescriptorHeapPool::D3D12DescriptorHeapPool(
ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32_t page_size)
: device_(device), type_(type), page_size_(page_size) {}
D3D12DescriptorHeapPool::~D3D12DescriptorHeapPool() { ClearCache(); }
void D3D12DescriptorHeapPool::Reclaim(uint64_t completed_submission_index) {
while (submitted_first_) {
if (submitted_first_->last_submission_index > completed_submission_index) {
break;
}
if (writable_last_) {
writable_last_->next = submitted_first_;
} else {
writable_first_ = submitted_first_;
}
writable_last_ = submitted_first_;
submitted_first_ = submitted_first_->next;
writable_last_->next = nullptr;
}
if (!submitted_first_) {
submitted_last_ = nullptr;
}
}
void D3D12DescriptorHeapPool::ClearCache() {
// Not checking current_page_used_ != 0 because asking for 0 descriptors
// returns a valid heap also - but actually the new heap will be different now
// and the old one must be unbound since it doesn't exist anymore.
++current_heap_index_;
current_page_used_ = 0;
while (submitted_first_) {
auto next = submitted_first_->next;
submitted_first_->heap->Release();
delete submitted_first_;
submitted_first_ = next;
}
submitted_last_ = nullptr;
while (writable_first_) {
auto next = writable_first_->next;
writable_first_->heap->Release();
delete writable_first_;
writable_first_ = next;
}
writable_last_ = nullptr;
}
uint64_t D3D12DescriptorHeapPool::Request(uint64_t submission_index,
uint64_t previous_heap_index,
uint32_t count_for_partial_update,
uint32_t count_for_full_update,
uint32_t& index_out) {
assert_true(count_for_partial_update <= count_for_full_update);
assert_true(count_for_full_update <= page_size_);
if (count_for_partial_update > count_for_full_update ||
count_for_full_update > page_size_) {
return kHeapIndexInvalid;
}
assert_true(!current_page_used_ ||
submission_index >= writable_first_->last_submission_index);
assert_true(!submitted_last_ ||
submission_index >= submitted_last_->last_submission_index);
// If the last full update happened on the current page, a partial update is
// possible.
uint32_t count = previous_heap_index == current_heap_index_
? count_for_partial_update
: count_for_full_update;
// Go to the next page if there's not enough free space on the current one,
// or because the previous page may be outdated. In this case, a full update
// is necessary.
if (page_size_ - current_page_used_ < count) {
// Close the page that was current.
if (submitted_last_) {
submitted_last_->next = writable_first_;
} else {
submitted_first_ = writable_first_;
}
submitted_last_ = writable_first_;
writable_first_ = writable_first_->next;
submitted_last_->next = nullptr;
if (!writable_first_) {
writable_last_ = nullptr;
}
++current_heap_index_;
current_page_used_ = 0;
count = count_for_full_update;
}
// Create the page if needed (may be the first call for the page).
if (!writable_first_) {
D3D12_DESCRIPTOR_HEAP_DESC new_heap_desc;
new_heap_desc.Type = type_;
new_heap_desc.NumDescriptors = page_size_;
new_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
new_heap_desc.NodeMask = 0;
ID3D12DescriptorHeap* new_heap;
if (FAILED(device_->CreateDescriptorHeap(&new_heap_desc,
IID_PPV_ARGS(&new_heap)))) {
XELOGE("Failed to create a heap for {} shader-visible descriptors",
page_size_);
return kHeapIndexInvalid;
}
writable_first_ = new Page;
writable_first_->heap = new_heap;
writable_first_->cpu_start = new_heap->GetCPUDescriptorHandleForHeapStart();
writable_first_->gpu_start = new_heap->GetGPUDescriptorHandleForHeapStart();
writable_first_->last_submission_index = submission_index;
writable_first_->next = nullptr;
writable_last_ = writable_first_;
}
writable_first_->last_submission_index = submission_index;
index_out = current_page_used_;
current_page_used_ += count;
return current_heap_index_;
}
} // namespace d3d12
} // namespace ui
} // namespace xe

View File

@@ -2,13 +2,13 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2018 Ben Vanik. All rights reserved. *
* Copyright 2020 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_
#ifndef XENIA_UI_D3D12_D3D12_DESCRIPTOR_HEAP_POOL_H_
#define XENIA_UI_D3D12_D3D12_DESCRIPTOR_HEAP_POOL_H_
#include <cstdint>
@@ -21,60 +21,13 @@ namespace d3d12 {
// Submission index is the fence value or a value derived from it (if reclaiming
// less often than once per fence value, for instance).
class UploadBufferPool {
public:
// Taken from the Direct3D 12 MiniEngine sample (LinearAllocator
// kCpuAllocatorPageSize). Large enough for most cases.
static constexpr uint32_t kDefaultPageSize = 2 * 1024 * 1024;
UploadBufferPool(D3D12Provider& provider,
uint32_t page_size = kDefaultPageSize);
~UploadBufferPool();
void Reclaim(uint64_t completed_submission_index);
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* Request(uint64_t submission_index, uint32_t size, uint32_t alignment,
ID3D12Resource** buffer_out, uint32_t* offset_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out);
// Request to write data in multiple parts, filling the buffer entirely.
uint8_t* RequestPartial(uint64_t submission_index, uint32_t size,
uint32_t alignment, ID3D12Resource** buffer_out,
uint32_t* offset_out, uint32_t* size_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out);
private:
D3D12Provider& provider_;
uint32_t page_size_;
struct Page {
ID3D12Resource* buffer;
D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
void* mapping;
uint64_t last_submission_index;
Page* next;
};
// A list of buffers with free space, with the first buffer being the one
// currently being filled.
Page* writable_first_ = nullptr;
Page* writable_last_ = nullptr;
// A list of full buffers that can be reclaimed when the GPU doesn't use them
// anymore.
Page* submitted_first_ = nullptr;
Page* submitted_last_ = nullptr;
uint32_t current_page_used_ = 0;
};
class DescriptorHeapPool {
class D3D12DescriptorHeapPool {
public:
static constexpr uint64_t kHeapIndexInvalid = UINT64_MAX;
DescriptorHeapPool(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type,
uint32_t page_size);
~DescriptorHeapPool();
D3D12DescriptorHeapPool(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type,
uint32_t page_size);
~D3D12DescriptorHeapPool();
void Reclaim(uint64_t completed_submission_index);
void ClearCache();
@@ -153,4 +106,4 @@ class DescriptorHeapPool {
} // namespace ui
} // namespace xe
#endif // XENIA_UI_D3D12_POOLS_H_
#endif // XENIA_UI_D3D12_D3D12_UPLOAD_BUFFER_POOL_H_

View File

@@ -287,10 +287,11 @@ bool D3D12ImmediateDrawer::Initialize() {
device->CreateSampler(&sampler_desc, sampler_handle);
// Create pools for draws.
vertex_buffer_pool_ = std::make_unique<UploadBufferPool>(provider);
texture_descriptor_pool_ = std::make_unique<DescriptorHeapPool>(
vertex_buffer_pool_ = std::make_unique<D3D12UploadBufferPool>(provider);
texture_descriptor_pool_ = std::make_unique<D3D12DescriptorHeapPool>(
device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 2048);
texture_descriptor_pool_heap_index_ = DescriptorHeapPool::kHeapIndexInvalid;
texture_descriptor_pool_heap_index_ =
D3D12DescriptorHeapPool::kHeapIndexInvalid;
// Reset the current state.
current_command_list_ = nullptr;
@@ -465,7 +466,8 @@ void D3D12ImmediateDrawer::Begin(int render_target_width,
vertex_buffer_pool_->Reclaim(completed_fence_value);
texture_descriptor_pool_->Reclaim(completed_fence_value);
texture_descriptor_pool_heap_index_ = DescriptorHeapPool::kHeapIndexInvalid;
texture_descriptor_pool_heap_index_ =
D3D12DescriptorHeapPool::kHeapIndexInvalid;
current_render_target_width_ = render_target_width;
current_render_target_height_ = render_target_height;
@@ -567,7 +569,7 @@ void D3D12ImmediateDrawer::Draw(const ImmediateDraw& draw) {
uint64_t texture_heap_index = texture_descriptor_pool_->Request(
context_.GetSwapCurrentFenceValue(), texture_descriptor_pool_heap_index_,
bind_texture ? 1 : 0, 1, texture_descriptor_index);
if (texture_heap_index == DescriptorHeapPool::kHeapIndexInvalid) {
if (texture_heap_index == D3D12DescriptorHeapPool::kHeapIndexInvalid) {
return;
}
if (texture_descriptor_pool_heap_index_ != texture_heap_index) {

View File

@@ -15,7 +15,8 @@
#include <vector>
#include "xenia/ui/d3d12/d3d12_api.h"
#include "xenia/ui/d3d12/pools.h"
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.h"
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
#include "xenia/ui/immediate_drawer.h"
namespace xe {
@@ -74,8 +75,8 @@ class D3D12ImmediateDrawer : public ImmediateDrawer {
D3D12_CPU_DESCRIPTOR_HANDLE sampler_heap_cpu_start_;
D3D12_GPU_DESCRIPTOR_HANDLE sampler_heap_gpu_start_;
std::unique_ptr<UploadBufferPool> vertex_buffer_pool_ = nullptr;
std::unique_ptr<DescriptorHeapPool> texture_descriptor_pool_ = nullptr;
std::unique_ptr<D3D12UploadBufferPool> vertex_buffer_pool_ = nullptr;
std::unique_ptr<D3D12DescriptorHeapPool> texture_descriptor_pool_ = nullptr;
uint64_t texture_descriptor_pool_heap_index_;
struct PendingTextureUpload {

View File

@@ -0,0 +1,120 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/d3d12/d3d12_util.h"
namespace xe {
namespace ui {
namespace d3d12 {
// Align to D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT not to waste any space if
// it's smaller (the size of the heap backing the buffer will be aligned to
// D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT anyway).
D3D12UploadBufferPool::D3D12UploadBufferPool(D3D12Provider& provider,
size_t page_size)
: GraphicsUploadBufferPool(xe::align(
page_size, size_t(D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT))),
provider_(provider) {}
uint8_t* D3D12UploadBufferPool::Request(
uint64_t submission_index, size_t size, size_t alignment,
ID3D12Resource** buffer_out, size_t* offset_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out) {
size_t offset;
const D3D12Page* page =
static_cast<const D3D12Page*>(GraphicsUploadBufferPool::Request(
submission_index, size, alignment, offset));
if (!page) {
return nullptr;
}
if (buffer_out) {
*buffer_out = page->buffer_;
}
if (offset_out) {
*offset_out = offset;
}
if (gpu_address_out) {
*gpu_address_out = page->gpu_address_ + offset;
}
return reinterpret_cast<uint8_t*>(page->mapping_) + offset;
}
uint8_t* D3D12UploadBufferPool::RequestPartial(
uint64_t submission_index, size_t size, size_t alignment,
ID3D12Resource** buffer_out, size_t* offset_out, size_t* size_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out) {
size_t offset, size_obtained;
const D3D12Page* page =
static_cast<const D3D12Page*>(GraphicsUploadBufferPool::RequestPartial(
submission_index, size, alignment, offset, size_obtained));
if (!page) {
return nullptr;
}
if (buffer_out) {
*buffer_out = page->buffer_;
}
if (offset_out) {
*offset_out = offset;
}
if (size_out) {
*size_out = size_obtained;
}
if (gpu_address_out) {
*gpu_address_out = page->gpu_address_ + offset;
}
return reinterpret_cast<uint8_t*>(page->mapping_) + offset;
}
GraphicsUploadBufferPool::Page*
D3D12UploadBufferPool::CreatePageImplementation() {
D3D12_RESOURCE_DESC buffer_desc;
util::FillBufferResourceDesc(buffer_desc, page_size_,
D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* buffer;
if (FAILED(provider_.GetDevice()->CreateCommittedResource(
&util::kHeapPropertiesUpload, provider_.GetHeapFlagCreateNotZeroed(),
&buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
IID_PPV_ARGS(&buffer)))) {
XELOGE("Failed to create a D3D upload buffer with {} bytes", page_size_);
return nullptr;
}
D3D12_RANGE read_range;
read_range.Begin = 0;
read_range.End = 0;
void* mapping;
if (FAILED(buffer->Map(0, &read_range, &mapping))) {
XELOGE("Failed to map a D3D upload buffer with {} bytes", page_size_);
buffer->Release();
return nullptr;
}
D3D12Page* page = new D3D12Page(buffer, mapping);
// Owned by the page now.
buffer->Release();
return page;
}
D3D12UploadBufferPool::D3D12Page::D3D12Page(ID3D12Resource* buffer,
void* mapping)
: buffer_(buffer), mapping_(mapping) {
buffer_->AddRef();
gpu_address_ = buffer_->GetGPUVirtualAddress();
}
D3D12UploadBufferPool::D3D12Page::~D3D12Page() {
// Unmapping is done implicitly when the buffer is destroyed.
buffer_->Release();
}
} // namespace d3d12
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,57 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_D3D12_D3D12_UPLOAD_BUFFER_POOL_H_
#define XENIA_UI_D3D12_D3D12_UPLOAD_BUFFER_POOL_H_
#include "xenia/ui/d3d12/d3d12_provider.h"
#include "xenia/ui/graphics_upload_buffer_pool.h"
namespace xe {
namespace ui {
namespace d3d12 {
// Submission index is the fence value or a value derived from it (if reclaiming
// less often than once per fence value, for instance).
class D3D12UploadBufferPool : public GraphicsUploadBufferPool {
public:
D3D12UploadBufferPool(D3D12Provider& provider,
size_t page_size = kDefaultPageSize);
uint8_t* Request(uint64_t submission_index, size_t size, size_t alignment,
ID3D12Resource** buffer_out, size_t* offset_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out);
uint8_t* RequestPartial(uint64_t submission_index, size_t size,
size_t alignment, ID3D12Resource** buffer_out,
size_t* offset_out, size_t* size_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out);
protected:
Page* CreatePageImplementation() override;
private:
struct D3D12Page : public Page {
// Creates a reference to the buffer. It must not be unmapped until this
// D3D12Page is deleted.
D3D12Page(ID3D12Resource* buffer, void* mapping);
~D3D12Page() override;
ID3D12Resource* buffer_;
void* mapping_;
D3D12_GPU_VIRTUAL_ADDRESS gpu_address_;
};
D3D12Provider& provider_;
};
} // namespace d3d12
} // namespace ui
} // namespace xe
#endif // XENIA_UI_D3D12_D3D12_UPLOAD_BUFFER_POOL_H_

View File

@@ -1,302 +0,0 @@
/**
******************************************************************************
* 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"
#include "xenia/base/math.h"
#include "xenia/ui/d3d12/d3d12_util.h"
namespace xe {
namespace ui {
namespace d3d12 {
// Align to D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT not to waste any space if
// it's smaller (the size of the heap backing the buffer will be aligned to
// D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT anyway).
UploadBufferPool::UploadBufferPool(D3D12Provider& provider, uint32_t page_size)
: provider_(provider),
page_size_(xe::align(
page_size, uint32_t(D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT))) {}
UploadBufferPool::~UploadBufferPool() { ClearCache(); }
void UploadBufferPool::Reclaim(uint64_t completed_submission_index) {
while (submitted_first_) {
if (submitted_first_->last_submission_index > completed_submission_index) {
break;
}
if (writable_last_) {
writable_last_->next = submitted_first_;
} else {
writable_first_ = submitted_first_;
}
writable_last_ = submitted_first_;
submitted_first_ = submitted_first_->next;
writable_last_->next = nullptr;
}
if (!submitted_first_) {
submitted_last_ = nullptr;
}
}
void UploadBufferPool::ClearCache() {
current_page_used_ = 0;
// Deleting anyway, so assuming data not needed anymore.
D3D12_RANGE written_range;
written_range.Begin = 0;
written_range.End = 0;
while (submitted_first_) {
auto next = submitted_first_->next;
submitted_first_->buffer->Unmap(0, &written_range);
submitted_first_->buffer->Release();
delete submitted_first_;
submitted_first_ = next;
}
submitted_last_ = nullptr;
while (writable_first_) {
auto next = writable_first_->next;
writable_first_->buffer->Unmap(0, &written_range);
writable_first_->buffer->Release();
delete writable_first_;
writable_first_ = next;
}
writable_last_ = nullptr;
}
uint8_t* UploadBufferPool::Request(uint64_t submission_index, uint32_t size,
uint32_t alignment,
ID3D12Resource** buffer_out,
uint32_t* offset_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out) {
assert_not_zero(alignment);
assert_true(xe::is_pow2(alignment));
size = xe::align(size, alignment);
assert_true(size <= page_size_);
if (size > page_size_) {
return nullptr;
}
assert_true(!current_page_used_ ||
submission_index >= writable_first_->last_submission_index);
assert_true(!submitted_last_ ||
submission_index >= submitted_last_->last_submission_index);
uint32_t current_page_used_aligned = xe::align(current_page_used_, alignment);
if (current_page_used_aligned + size > page_size_ || !writable_first_) {
// Start a new page if can't fit all the bytes or don't have an open page.
if (writable_first_) {
// Close the page that was current.
if (submitted_last_) {
submitted_last_->next = writable_first_;
} else {
submitted_first_ = writable_first_;
}
submitted_last_ = writable_first_;
writable_first_ = writable_first_->next;
submitted_last_->next = nullptr;
if (!writable_first_) {
writable_last_ = nullptr;
}
}
if (!writable_first_) {
// Create a new page if none available.
D3D12_RESOURCE_DESC new_buffer_desc;
util::FillBufferResourceDesc(new_buffer_desc, page_size_,
D3D12_RESOURCE_FLAG_NONE);
ID3D12Resource* new_buffer;
if (FAILED(provider_.GetDevice()->CreateCommittedResource(
&util::kHeapPropertiesUpload,
provider_.GetHeapFlagCreateNotZeroed(), &new_buffer_desc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
IID_PPV_ARGS(&new_buffer)))) {
XELOGE("Failed to create a D3D upload buffer with {} bytes",
page_size_);
return nullptr;
}
D3D12_RANGE read_range;
read_range.Begin = 0;
read_range.End = 0;
void* new_buffer_mapping;
if (FAILED(new_buffer->Map(0, &read_range, &new_buffer_mapping))) {
XELOGE("Failed to map a D3D upload buffer with {} bytes", page_size_);
new_buffer->Release();
return nullptr;
}
writable_first_ = new Page;
writable_first_->buffer = new_buffer;
writable_first_->gpu_address = new_buffer->GetGPUVirtualAddress();
writable_first_->mapping = new_buffer_mapping;
writable_first_->last_submission_index = submission_index;
writable_first_->next = nullptr;
writable_last_ = writable_first_;
}
current_page_used_ = 0;
current_page_used_aligned = 0;
}
writable_first_->last_submission_index = submission_index;
if (buffer_out) {
*buffer_out = writable_first_->buffer;
}
if (offset_out) {
*offset_out = current_page_used_aligned;
}
if (gpu_address_out) {
*gpu_address_out = writable_first_->gpu_address + current_page_used_aligned;
}
uint8_t* mapping = reinterpret_cast<uint8_t*>(writable_first_->mapping) +
current_page_used_aligned;
current_page_used_ = current_page_used_aligned + size;
return mapping;
}
uint8_t* UploadBufferPool::RequestPartial(
uint64_t submission_index, uint32_t size, uint32_t alignment,
ID3D12Resource** buffer_out, uint32_t* offset_out, uint32_t* size_out,
D3D12_GPU_VIRTUAL_ADDRESS* gpu_address_out) {
assert_not_zero(alignment);
assert_true(xe::is_pow2(alignment));
size = xe::align(size, alignment);
size = std::min(size, page_size_);
uint32_t current_page_used_aligned = xe::align(current_page_used_, alignment);
if (current_page_used_aligned + alignment <= page_size_) {
size = std::min(
size, (page_size_ - current_page_used_aligned) & ~(alignment - 1));
}
uint8_t* mapping = Request(submission_index, size, alignment, buffer_out,
offset_out, gpu_address_out);
if (!mapping) {
return nullptr;
}
if (size_out) {
*size_out = size;
}
return mapping;
}
DescriptorHeapPool::DescriptorHeapPool(ID3D12Device* device,
D3D12_DESCRIPTOR_HEAP_TYPE type,
uint32_t page_size)
: device_(device), type_(type), page_size_(page_size) {}
DescriptorHeapPool::~DescriptorHeapPool() { ClearCache(); }
void DescriptorHeapPool::Reclaim(uint64_t completed_submission_index) {
while (submitted_first_) {
if (submitted_first_->last_submission_index > completed_submission_index) {
break;
}
if (writable_last_) {
writable_last_->next = submitted_first_;
} else {
writable_first_ = submitted_first_;
}
writable_last_ = submitted_first_;
submitted_first_ = submitted_first_->next;
writable_last_->next = nullptr;
}
if (!submitted_first_) {
submitted_last_ = nullptr;
}
}
void DescriptorHeapPool::ClearCache() {
// Not checking current_page_used_ != 0 because asking for 0 descriptors
// returns a valid heap also - but actually the new heap will be different now
// and the old one must be unbound since it doesn't exist anymore.
++current_heap_index_;
current_page_used_ = 0;
while (submitted_first_) {
auto next = submitted_first_->next;
submitted_first_->heap->Release();
delete submitted_first_;
submitted_first_ = next;
}
submitted_last_ = nullptr;
while (writable_first_) {
auto next = writable_first_->next;
writable_first_->heap->Release();
delete writable_first_;
writable_first_ = next;
}
writable_last_ = nullptr;
}
uint64_t DescriptorHeapPool::Request(uint64_t submission_index,
uint64_t previous_heap_index,
uint32_t count_for_partial_update,
uint32_t count_for_full_update,
uint32_t& index_out) {
assert_true(count_for_partial_update <= count_for_full_update);
assert_true(count_for_full_update <= page_size_);
if (count_for_partial_update > count_for_full_update ||
count_for_full_update > page_size_) {
return kHeapIndexInvalid;
}
assert_true(!current_page_used_ ||
submission_index >= writable_first_->last_submission_index);
assert_true(!submitted_last_ ||
submission_index >= submitted_last_->last_submission_index);
// If the last full update happened on the current page, a partial update is
// possible.
uint32_t count = previous_heap_index == current_heap_index_
? count_for_partial_update
: count_for_full_update;
// Go to the next page if there's not enough free space on the current one,
// or because the previous page may be outdated. In this case, a full update
// is necessary.
if (page_size_ - current_page_used_ < count) {
// Close the page that was current.
if (submitted_last_) {
submitted_last_->next = writable_first_;
} else {
submitted_first_ = writable_first_;
}
submitted_last_ = writable_first_;
writable_first_ = writable_first_->next;
submitted_last_->next = nullptr;
if (!writable_first_) {
writable_last_ = nullptr;
}
++current_heap_index_;
current_page_used_ = 0;
count = count_for_full_update;
}
// Create the page if needed (may be the first call for the page).
if (!writable_first_) {
D3D12_DESCRIPTOR_HEAP_DESC new_heap_desc;
new_heap_desc.Type = type_;
new_heap_desc.NumDescriptors = page_size_;
new_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
new_heap_desc.NodeMask = 0;
ID3D12DescriptorHeap* new_heap;
if (FAILED(device_->CreateDescriptorHeap(&new_heap_desc,
IID_PPV_ARGS(&new_heap)))) {
XELOGE("Failed to create a heap for {} shader-visible descriptors",
page_size_);
return kHeapIndexInvalid;
}
writable_first_ = new Page;
writable_first_->heap = new_heap;
writable_first_->cpu_start = new_heap->GetCPUDescriptorHandleForHeapStart();
writable_first_->gpu_start = new_heap->GetGPUDescriptorHandleForHeapStart();
writable_first_->last_submission_index = submission_index;
writable_first_->next = nullptr;
writable_last_ = writable_first_;
}
writable_first_->last_submission_index = submission_index;
index_out = current_page_used_;
current_page_used_ += count;
return current_heap_index_;
}
} // namespace d3d12
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,151 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/graphics_upload_buffer_pool.h"
#include <algorithm>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/ui/graphics_upload_buffer_pool.h"
namespace xe {
namespace ui {
GraphicsUploadBufferPool::~GraphicsUploadBufferPool() { ClearCache(); }
void GraphicsUploadBufferPool::Reclaim(uint64_t completed_submission_index) {
while (submitted_first_) {
if (submitted_first_->last_submission_index_ > completed_submission_index) {
break;
}
if (writable_last_) {
writable_last_->next_ = submitted_first_;
} else {
writable_first_ = submitted_first_;
}
writable_last_ = submitted_first_;
submitted_first_ = submitted_first_->next_;
writable_last_->next_ = nullptr;
}
if (!submitted_first_) {
submitted_last_ = nullptr;
}
}
void GraphicsUploadBufferPool::ClearCache() {
// Called from the destructor - must not call virtual functions here.
current_page_flushed_ = 0;
current_page_used_ = 0;
while (submitted_first_) {
Page* next_ = submitted_first_->next_;
delete submitted_first_;
submitted_first_ = next_;
}
submitted_last_ = nullptr;
while (writable_first_) {
Page* next_ = writable_first_->next_;
delete writable_first_;
writable_first_ = next_;
}
writable_last_ = nullptr;
}
GraphicsUploadBufferPool::Page::~Page() {}
void GraphicsUploadBufferPool::FlushWrites() {
if (current_page_flushed_ >= current_page_used_) {
return;
}
assert_not_null(writable_first_);
FlushPageWrites(writable_first_, current_page_flushed_,
current_page_used_ - current_page_flushed_);
current_page_flushed_ = current_page_used_;
}
GraphicsUploadBufferPool::Page* GraphicsUploadBufferPool::Request(
uint64_t submission_index, size_t size, size_t alignment,
size_t& offset_out) {
assert_not_zero(alignment);
assert_true(xe::is_pow2(alignment));
size = xe::align(size, alignment);
assert_true(size <= page_size_);
if (size > page_size_) {
return nullptr;
}
assert_true(!current_page_used_ ||
submission_index >= writable_first_->last_submission_index_);
assert_true(!submitted_last_ ||
submission_index >= submitted_last_->last_submission_index_);
size_t current_page_used_aligned = xe::align(current_page_used_, alignment);
if (current_page_used_aligned + size > page_size_ || !writable_first_) {
// Start a new page if can't fit all the bytes or don't have an open page.
if (writable_first_) {
// Close the page that was current.
FlushWrites();
if (submitted_last_) {
submitted_last_->next_ = writable_first_;
} else {
submitted_first_ = writable_first_;
}
submitted_last_ = writable_first_;
writable_first_ = writable_first_->next_;
submitted_last_->next_ = nullptr;
if (!writable_first_) {
writable_last_ = nullptr;
}
}
if (!writable_first_) {
// Create a new page if none available.
writable_first_ = CreatePageImplementation();
if (!writable_first_) {
// Failed to create.
return nullptr;
}
writable_first_->last_submission_index_ = submission_index;
writable_first_->next_ = nullptr;
writable_last_ = writable_first_;
// After CreatePageImplementation (more specifically, the first successful
// call), page_size_ may grow - but this doesn't matter here.
}
current_page_used_ = 0;
current_page_used_aligned = 0;
current_page_flushed_ = 0;
}
writable_first_->last_submission_index_ = submission_index;
offset_out = current_page_used_aligned;
current_page_used_ = current_page_used_aligned + size;
return writable_first_;
}
GraphicsUploadBufferPool::Page* GraphicsUploadBufferPool::RequestPartial(
uint64_t submission_index, size_t size, size_t alignment,
size_t& offset_out, size_t& size_out) {
assert_not_zero(alignment);
assert_true(xe::is_pow2(alignment));
size = xe::align(size, alignment);
size = std::min(size, page_size_);
size_t current_page_used_aligned = xe::align(current_page_used_, alignment);
if (current_page_used_aligned + alignment <= page_size_) {
size = std::min(
size, (page_size_ - current_page_used_aligned) & ~(alignment - 1));
}
Page* page = Request(submission_index, size, alignment, offset_out);
if (!page) {
return nullptr;
}
size_out = size;
return page;
}
void GraphicsUploadBufferPool::FlushPageWrites(Page* page, size_t offset,
size_t size) {}
} // namespace ui
} // namespace xe

View File

@@ -0,0 +1,79 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_GRAPHICS_UPLOAD_BUFFER_POOL_H_
#define XENIA_UI_GRAPHICS_UPLOAD_BUFFER_POOL_H_
#include <cstdint>
namespace xe {
namespace ui {
// Submission index is the fence value or a value derived from it (if reclaiming
// less often than once per fence value, for instance).
class GraphicsUploadBufferPool {
public:
// Taken from the Direct3D 12 MiniEngine sample (LinearAllocator
// kCpuAllocatorPageSize). Large enough for most cases.
static constexpr size_t kDefaultPageSize = 2 * 1024 * 1024;
virtual ~GraphicsUploadBufferPool();
void Reclaim(uint64_t completed_submission_index);
void ClearCache();
// Should be called before submitting anything using this pool, unless the
// implementation doesn't require explicit flushing.
void FlushWrites();
protected:
// Extended by the implementation.
struct Page {
virtual ~Page();
uint64_t last_submission_index_;
Page* next_;
};
GraphicsUploadBufferPool(size_t page_size) : page_size_(page_size) {}
// Request to write data in a single piece, creating a new page if the current
// one doesn't have enough free space.
Page* Request(uint64_t submission_index, size_t size, size_t alignment,
size_t& offset_out);
// Request to write data in multiple parts, filling the buffer entirely.
Page* RequestPartial(uint64_t submission_index, size_t size, size_t alignment,
size_t& offset_out, size_t& size_out);
virtual Page* CreatePageImplementation() = 0;
virtual void FlushPageWrites(Page* page, size_t offset, size_t size);
// May be increased by the implementation on creation or on first allocation
// to avoid wasting space if the real allocation turns out to be bigger than
// the specified page size.
size_t page_size_;
// A list of buffers with free space, with the first buffer being the one
// currently being filled.
Page* writable_first_ = nullptr;
Page* writable_last_ = nullptr;
// A list of full buffers that can be reclaimed when the GPU doesn't use them
// anymore.
Page* submitted_first_ = nullptr;
Page* submitted_last_ = nullptr;
size_t current_page_used_ = 0;
size_t current_page_flushed_ = 0;
};
} // namespace ui
} // namespace xe
#endif // XENIA_UI_GRAPHICS_UPLOAD_BUFFER_POOL_H_