[D3D12] Texture invalidation (incomplete)

This commit is contained in:
Triang3l 2018-08-18 15:23:48 +03:00
parent 9e21f5ab67
commit bfb781d293
4 changed files with 96 additions and 18 deletions

View File

@ -130,7 +130,7 @@ SharedMemory::WatchHandle SharedMemory::WatchMemoryRange(
uint32_t bucket_last = uint32_t bucket_last =
watch_page_last << page_size_log2_ >> kWatchBucketSizeLog2; watch_page_last << page_size_log2_ >> kWatchBucketSizeLog2;
std::lock_guard<std::mutex> lock(validity_mutex_); std::lock_guard<std::recursive_mutex> lock(validity_mutex_);
// Allocate the range. // Allocate the range.
WatchRange* range = watch_range_first_free_; WatchRange* range = watch_range_first_free_;
@ -141,9 +141,8 @@ SharedMemory::WatchHandle SharedMemory::WatchMemoryRange(
watch_range_current_pool_allocated_ >= kWatchRangePoolSize) { watch_range_current_pool_allocated_ >= kWatchRangePoolSize) {
watch_range_pools_.push_back(new WatchRange[kWatchRangePoolSize]); watch_range_pools_.push_back(new WatchRange[kWatchRangePoolSize]);
watch_range_current_pool_allocated_ = 0; watch_range_current_pool_allocated_ = 0;
range =
&(watch_range_pools_.back()[watch_range_current_pool_allocated_++]);
} }
range = &(watch_range_pools_.back()[watch_range_current_pool_allocated_++]);
} }
range->callback = callback; range->callback = callback;
range->callback_context = callback_context; range->callback_context = callback_context;
@ -163,9 +162,8 @@ SharedMemory::WatchHandle SharedMemory::WatchMemoryRange(
watch_node_current_pool_allocated_ >= kWatchNodePoolSize) { watch_node_current_pool_allocated_ >= kWatchNodePoolSize) {
watch_node_pools_.push_back(new WatchNode[kWatchNodePoolSize]); watch_node_pools_.push_back(new WatchNode[kWatchNodePoolSize]);
watch_node_current_pool_allocated_ = 0; watch_node_current_pool_allocated_ = 0;
node =
&(watch_node_pools_.back()[watch_node_current_pool_allocated_++]);
} }
node = &(watch_node_pools_.back()[watch_node_current_pool_allocated_++]);
} }
node->range = range; node->range = range;
node->range_node_next = nullptr; node->range_node_next = nullptr;
@ -186,6 +184,15 @@ SharedMemory::WatchHandle SharedMemory::WatchMemoryRange(
return reinterpret_cast<WatchHandle>(range); return reinterpret_cast<WatchHandle>(range);
} }
void SharedMemory::UnwatchMemoryRange(WatchHandle handle) {
if (handle == nullptr) {
// Could be a zero length range.
return;
}
std::lock_guard<std::recursive_mutex> lock(validity_mutex_);
UnlinkWatchRange(reinterpret_cast<WatchRange*>(handle));
}
bool SharedMemory::RequestRange(uint32_t start, uint32_t length, bool SharedMemory::RequestRange(uint32_t start, uint32_t length,
ID3D12GraphicsCommandList* command_list) { ID3D12GraphicsCommandList* command_list) {
if (length == 0) { if (length == 0) {
@ -299,7 +306,7 @@ void SharedMemory::MakeRangeValid(uint32_t valid_page_first,
uint32_t valid_block_first = valid_page_first >> 6; uint32_t valid_block_first = valid_page_first >> 6;
uint32_t valid_block_last = valid_page_last >> 6; uint32_t valid_block_last = valid_page_last >> 6;
std::lock_guard<std::mutex> lock(validity_mutex_); std::lock_guard<std::recursive_mutex> lock(validity_mutex_);
for (uint32_t i = valid_block_first; i <= valid_block_last; ++i) { for (uint32_t i = valid_block_first; i <= valid_block_last; ++i) {
uint64_t valid_bits = UINT64_MAX; uint64_t valid_bits = UINT64_MAX;
@ -353,7 +360,7 @@ void SharedMemory::GetRangesToUpload(uint32_t request_page_first,
uint32_t request_block_first = request_page_first >> 6; uint32_t request_block_first = request_page_first >> 6;
uint32_t request_block_last = request_page_last >> 6; uint32_t request_block_last = request_page_last >> 6;
std::lock_guard<std::mutex> lock(validity_mutex_); std::lock_guard<std::recursive_mutex> lock(validity_mutex_);
uint32_t range_start = UINT32_MAX; uint32_t range_start = UINT32_MAX;
for (uint32_t i = request_block_first; i <= request_block_last; ++i) { for (uint32_t i = request_block_first; i <= request_block_last; ++i) {
@ -414,14 +421,28 @@ bool SharedMemory::MemoryWriteCallback(uint32_t address) {
uint32_t block_index = page_index >> 6; uint32_t block_index = page_index >> 6;
uint64_t page_bit = 1ull << (page_index & 63); uint64_t page_bit = 1ull << (page_index & 63);
std::lock_guard<std::mutex> lock(validity_mutex_); std::lock_guard<std::recursive_mutex> lock(validity_mutex_);
if (!(protected_pages_[block_index] & page_bit)) { if (!(protected_pages_[block_index] & page_bit)) {
return false; return false;
} }
valid_pages_[block_index] &= ~page_bit; valid_pages_[block_index] &= ~page_bit;
// TODO(Triang3l): Invoke watch callbacks.
// Trigger watch callbacks.
WatchNode* node =
watch_buckets_[page_index << page_size_log2_ >> kWatchBucketSizeLog2];
while (node != nullptr) {
WatchRange* range = node->range;
// Store the next node now since when the callback is triggered, the links
// will be broken.
node = node->bucket_node_next;
if (page_index >= range->page_first && page_index <= range->page_last) {
range->callback(range->callback_context, range->callback_data,
range->callback_argument);
UnlinkWatchRange(range);
}
}
memory_->UnprotectPhysicalMemory(page_index << page_size_log2_, memory_->UnprotectPhysicalMemory(page_index << page_size_log2_,
1 << page_size_log2_, false); 1 << page_size_log2_, false);

View File

@ -56,10 +56,16 @@ class SharedMemory {
// //
// The callback is called with the mutex locked. Do NOT watch or unwatch // The callback is called with the mutex locked. Do NOT watch or unwatch
// ranges from within it! The watch for the callback is cancelled after the // ranges from within it! The watch for the callback is cancelled after the
// callback. // callback - the handle becomes invalid.
WatchHandle WatchMemoryRange(uint32_t start, uint32_t length, WatchHandle WatchMemoryRange(uint32_t start, uint32_t length,
WatchCallback callback, void* callback_context, WatchCallback callback, void* callback_context,
void* callback_data, uint64_t callback_argument); void* callback_data, uint64_t callback_argument);
// Unregisters previously registered watched memory range.
void UnwatchMemoryRange(WatchHandle handle);
// Locks the mutex that gets locked when watch callbacks are invoked - must be
// done when checking variables that may be changed by a watch callback.
inline void LockWatchMutex() { validity_mutex_.lock(); }
inline void UnlockWatchMutex() { validity_mutex_.unlock(); }
// Checks if the range has been updated, uploads new data if needed and // 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 // ensures the buffer tiles backing the range are resident. May transition the
@ -108,7 +114,7 @@ class SharedMemory {
// Mutex between the exception handler and the command processor, to be locked // Mutex between the exception handler and the command processor, to be locked
// when checking or updating validity of pages/ranges. // when checking or updating validity of pages/ranges.
std::mutex validity_mutex_; std::recursive_mutex validity_mutex_;
// *************************************************************************** // ***************************************************************************
// Things below should be protected by validity_mutex_. // Things below should be protected by validity_mutex_.

View File

@ -701,6 +701,8 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
texture->base_size *= key.depth; texture->base_size *= key.depth;
texture->mip_size *= key.depth; texture->mip_size *= key.depth;
} }
texture->base_watch_handle = nullptr;
texture->mip_watch_handle = nullptr;
textures_.insert(std::make_pair(map_key, texture)); textures_.insert(std::make_pair(map_key, texture));
LogTextureAction(texture, "Created"); LogTextureAction(texture, "Created");
@ -708,7 +710,12 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) {
} }
bool TextureCache::LoadTextureData(Texture* texture) { bool TextureCache::LoadTextureData(Texture* texture) {
if (texture->base_in_sync && texture->mips_in_sync) { // See what we need to upload.
shared_memory_->LockWatchMutex();
bool base_in_sync = texture->base_in_sync;
bool mips_in_sync = texture->mips_in_sync;
shared_memory_->UnlockWatchMutex();
if (base_in_sync && mips_in_sync) {
return true; return true;
} }
@ -731,13 +738,13 @@ bool TextureCache::LoadTextureData(Texture* texture) {
} }
// Request uploading of the texture data to the shared memory. // Request uploading of the texture data to the shared memory.
if (!texture->base_in_sync) { if (!base_in_sync) {
if (!shared_memory_->RequestRange(texture->key.base_page << 12, if (!shared_memory_->RequestRange(texture->key.base_page << 12,
texture->base_size, command_list)) { texture->base_size, command_list)) {
return false; return false;
} }
} }
if (!texture->mips_in_sync) { if (!mips_in_sync) {
if (!shared_memory_->RequestRange(texture->key.mip_page << 12, if (!shared_memory_->RequestRange(texture->key.mip_page << 12,
texture->mip_size, command_list)) { texture->mip_size, command_list)) {
return false; return false;
@ -811,8 +818,8 @@ bool TextureCache::LoadTextureData(Texture* texture) {
command_list->ResourceBarrier(1, barriers); command_list->ResourceBarrier(1, barriers);
texture->state = D3D12_RESOURCE_STATE_COPY_DEST; texture->state = D3D12_RESOURCE_STATE_COPY_DEST;
} }
uint32_t mip_first = texture->base_in_sync ? 1 : 0; uint32_t mip_first = base_in_sync ? 1 : 0;
uint32_t mip_last = texture->mips_in_sync ? 0 : resource_desc.MipLevels - 1; uint32_t mip_last = mips_in_sync ? 0 : resource_desc.MipLevels - 1;
auto cbuffer_pool = command_processor_->GetConstantBufferPool(); auto cbuffer_pool = command_processor_->GetConstantBufferPool();
CopyConstants copy_constants; CopyConstants copy_constants;
copy_constants.is_3d = is_3d ? 1 : 0; copy_constants.is_3d = is_3d ? 1 : 0;
@ -903,13 +910,46 @@ bool TextureCache::LoadTextureData(Texture* texture) {
command_processor_->ReleaseScratchGPUBuffer(copy_buffer, copy_buffer_state); command_processor_->ReleaseScratchGPUBuffer(copy_buffer, copy_buffer_state);
// Mark the ranges as uploaded and watch them.
shared_memory_->LockWatchMutex();
texture->base_in_sync = true; texture->base_in_sync = true;
texture->mips_in_sync = true; texture->mips_in_sync = true;
if (!base_in_sync) {
texture->base_watch_handle = shared_memory_->WatchMemoryRange(
texture->key.base_page << 12, texture->base_size, WatchCallbackThunk,
this, texture, 0);
}
if (!mips_in_sync) {
texture->mip_watch_handle = shared_memory_->WatchMemoryRange(
texture->key.mip_page << 12, texture->mip_size, WatchCallbackThunk,
this, texture, 1);
}
shared_memory_->UnlockWatchMutex();
LogTextureAction(texture, "Loaded"); LogTextureAction(texture, "Loaded");
return true; return true;
} }
void TextureCache::WatchCallbackThunk(void* context, void* data,
uint64_t argument) {
TextureCache* texture_cache = reinterpret_cast<TextureCache*>(context);
texture_cache->WatchCallback(reinterpret_cast<Texture*>(data), argument != 0);
}
void TextureCache::WatchCallback(Texture* texture, bool is_mip) {
// Mutex already locked here.
if (is_mip) {
texture->mips_in_sync = false;
texture->mip_watch_handle = nullptr;
} else {
texture->base_in_sync = false;
texture->base_watch_handle = nullptr;
}
XELOGE("Texture %s at %.8X invalidated", is_mip ? "mips" : "base",
(is_mip ? texture->key.mip_page : texture->key.base_page) << 12);
// TODO(Triang3l): Notify bindings that ranges should be requested again.
}
void TextureCache::ClearBindings() { void TextureCache::ClearBindings() {
std::memset(texture_bindings_, 0, sizeof(texture_bindings_)); std::memset(texture_bindings_, 0, sizeof(texture_bindings_));
texture_keys_in_sync_ = 0; texture_keys_in_sync_ = 0;

View File

@ -181,9 +181,16 @@ class TextureCache {
uint32_t mip_offsets[14]; uint32_t mip_offsets[14];
// Byte pitches of each mipmap within one slice (for linear layout mainly). // Byte pitches of each mipmap within one slice (for linear layout mainly).
uint32_t mip_pitches[14]; uint32_t mip_pitches[14];
// Whether the recent base level data has been loaded from the memory.
// Watch handles for the memory ranges (protected by the shared memory watch
// mutex).
SharedMemory::WatchHandle base_watch_handle;
SharedMemory::WatchHandle mip_watch_handle;
// Whether the recent base level data has been loaded from the memory
// (protected by the shared memory watch mutex).
bool base_in_sync; bool base_in_sync;
// Whether the recent mip data has been loaded from the memory. // Whether the recent mip data has been loaded from the memory (protected by
// the shared memory watch mutex).
bool mips_in_sync; bool mips_in_sync;
}; };
@ -234,6 +241,10 @@ class TextureCache {
// allocates descriptors and copies! // allocates descriptors and copies!
bool LoadTextureData(Texture* texture); bool LoadTextureData(Texture* texture);
// Shared memory callback for texture data invalidation.
static void WatchCallbackThunk(void* context, void* data, uint64_t argument);
void WatchCallback(Texture* texture, bool is_mip);
// Makes all bindings invalid. Also requesting textures after calling this // Makes all bindings invalid. Also requesting textures after calling this
// will cause another attempt to create a texture or to untile it if there was // will cause another attempt to create a texture or to untile it if there was
// an error. // an error.