[D3D12] Shared memory watch structures

This commit is contained in:
Triang3l 2018-08-17 21:41:18 +03:00
parent e6ec390a04
commit 005040e885
2 changed files with 87 additions and 20 deletions

View File

@ -30,7 +30,7 @@ SharedMemory::SharedMemory(Memory* memory, ui::d3d12::D3D12Context* context)
assert_true(page_bitmap_length != 0);
valid_pages_.resize(page_bitmap_length);
watched_pages_.resize(page_bitmap_length);
protected_pages_.resize(page_bitmap_length);
}
SharedMemory::~SharedMemory() { Shutdown(); }
@ -76,13 +76,13 @@ bool SharedMemory::Initialize() {
std::memset(valid_pages_.data(), 0, valid_pages_.size() * sizeof(uint64_t));
std::memset(watched_pages_.data(), 0,
watched_pages_.size() * sizeof(uint64_t));
std::memset(protected_pages_.data(), 0,
protected_pages_.size() * sizeof(uint64_t));
upload_buffer_pool_ =
std::make_unique<ui::d3d12::UploadBufferPool>(context_, 4 * 1024 * 1024);
memory_->SetGlobalPhysicalAccessWatch(WatchCallbackThunk, this);
memory_->SetGlobalPhysicalAccessWatch(MemoryWriteCallbackThunk, this);
return true;
}
@ -179,7 +179,7 @@ bool SharedMemory::RequestRange(uint32_t start, uint32_t length,
}
}
// Upload and watch used ranges.
// Upload and protect used ranges.
GetRangesToUpload(start >> page_size_log2_,
((start & ((1 << page_size_log2_) - 1)) + length +
((1 << page_size_log2_) - 1)) >>
@ -239,7 +239,7 @@ void SharedMemory::MakeRangeValid(uint32_t valid_page_first,
valid_bits &= (1ull << ((valid_page_last & 63) + 1)) - 1;
}
valid_pages_[i] |= valid_bits;
watched_pages_[i] |= valid_bits;
protected_pages_[i] |= valid_bits;
}
memory_->ProtectPhysicalMemory(
@ -309,27 +309,29 @@ void SharedMemory::GetRangesToUpload(uint32_t request_page_first,
}
}
bool SharedMemory::WatchCallbackThunk(void* context_ptr, uint32_t address) {
return reinterpret_cast<SharedMemory*>(context_ptr)->WatchCallback(address);
bool SharedMemory::MemoryWriteCallbackThunk(void* context_ptr,
uint32_t address) {
SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(context_ptr);
return shared_memory->MemoryWriteCallback(address);
}
bool SharedMemory::WatchCallback(uint32_t address) {
bool SharedMemory::MemoryWriteCallback(uint32_t address) {
uint32_t page_index = (address & kAddressMask) >> page_size_log2_;
uint32_t block_index = page_index >> 6;
uint64_t page_bit = 1ull << (page_index & 63);
std::lock_guard<std::mutex> lock(validity_mutex_);
if (!(watched_pages_[block_index] & page_bit)) {
if (!(protected_pages_[block_index] & page_bit)) {
return false;
}
valid_pages_[block_index] &= ~page_bit;
// TODO(Triang3l): Invoke texture invalidation callbacks.
// TODO(Triang3l): Invoke watch callbacks.
memory_->UnprotectPhysicalMemory(page_index << page_size_log2_,
1 << page_size_log2_, false);
watched_pages_[block_index] &= ~page_bit;
protected_pages_[block_index] &= ~page_bit;
return true;
}

View File

@ -43,6 +43,18 @@ class SharedMemory {
// The draw command list is needed for the transition.
void EndFrame();
typedef void (*WatchCallback)(void* context, void* data, uint64_t argument);
typedef void* WatchHandle;
// Registers a callback invoked when something is written to the specified
// memory range by the CPU or (if triggered explicitly - such as by a resolve)
// the GPU. Generally the context is the subsystem pointer (for example, the
// texture cache), the data is the object (such as a texture), and the
// argument is additional subsystem/object-specific data (such as whether the
// range belongs to the base mip level or to the rest of the mips).
WatchHandle WatchMemoryRange(uint32_t start, uint32_t length,
WatchCallback callback, void* callback_context,
void* callback_data, uint64_t callback_argument);
// Checks if the range has been updated, uploads new data if needed and
// ensures the buffer tiles backing the range are resident. May transition the
// tiled buffer to copy destination - call this before UseForReading or
@ -91,11 +103,71 @@ class SharedMemory {
// Mutex between the exception handler and the command processor, to be locked
// when checking or updating validity of pages/ranges.
std::mutex validity_mutex_;
// ***************************************************************************
// Things below should be protected by validity_mutex_.
// ***************************************************************************
// Bit vector containing whether physical memory system pages are up to date.
std::vector<uint64_t> valid_pages_;
// Mark the memory range as updated and watch it.
// Mark the memory range as updated and protect it.
void MakeRangeValid(uint32_t valid_page_first, uint32_t valid_page_count);
// Whether each physical page is protected by the GPU code (after uploading).
std::vector<uint64_t> protected_pages_;
// Memory access callback.
static bool MemoryWriteCallbackThunk(void* context_ptr, uint32_t address);
bool MemoryWriteCallback(uint32_t address);
// Watched range placed by other GPU subsystems.
struct WatchRange {
WatchCallback callback;
void* callback_context;
void* callback_data;
uint64_t callback_argument;
struct WatchNode* node_first;
uint32_t page_first;
uint32_t page_last;
};
// Node for faster checking of watches when pages have been written to - all
// 512 MB are split into smaller equally sized buckets, and then ranges are
// linearly checked.
struct WatchNode {
WatchRange* range;
// Links to nodes belonging to other watched ranges in the bucket.
WatchNode* bucket_node_previous;
WatchNode* bucket_node_next;
// Link to another node of this watched range in the next bucket.
WatchNode* range_node_next;
};
static constexpr uint32_t kWatchBucketSizeLog2 = 22;
static constexpr uint32_t kWatchBucketCount =
1 << (kBufferSizeLog2 - kWatchBucketSizeLog2);
WatchNode* watch_buckets_[kWatchBucketCount] = {};
// Allocations in pools - taking new WatchRanges and WatchNodes from the free
// list, and if there are none, creating a pool if the current one is fully
// used, and linearly allocating from the current pool.
union WatchRangeAllocation {
WatchRange range;
WatchRangeAllocation* next_free;
};
union WatchNodeAllocation {
WatchNode node;
WatchNodeAllocation* next_free;
};
static constexpr uint32_t kWatchRangePoolSize = 8192;
static constexpr uint32_t kWatchNodePoolSize = 8192;
std::vector<WatchRangeAllocation*> watch_range_pools_;
std::vector<WatchNodeAllocation*> watch_node_pools_;
uint32_t watch_range_current_pool_allocated_ = 0;
uint32_t watch_node_current_pool_allocated_ = 0;
WatchRangeAllocation* watch_range_first_free = nullptr;
WatchNodeAllocation* watch_node_first_free = nullptr;
// ***************************************************************************
// Things above should be protected by validity_mutex_.
// ***************************************************************************
// First page and length in pages.
typedef std::pair<uint32_t, uint32_t> UploadRange;
// Ranges that need to be uploaded, generated by GetRangesToUpload (a
@ -105,13 +177,6 @@ class SharedMemory {
uint32_t request_page_count);
std::unique_ptr<ui::d3d12::UploadBufferPool> upload_buffer_pool_ = nullptr;
// Whether each physical page is watched by the GPU (after uploading).
// Once a watch is triggered, it's not watched anymore.
std::vector<uint64_t> watched_pages_;
// Memory access callback.
static bool WatchCallbackThunk(void* context_ptr, uint32_t address);
bool WatchCallback(uint32_t address);
void TransitionBuffer(D3D12_RESOURCE_STATES new_state,
ID3D12GraphicsCommandList* command_list);
};